LeadPass Docs

Webhooks

The moment LeadPass decides a lead's verdict, it can POST the full result — verdict, summary, and the evidence per criterion — to a URL you choose. That means qualified leads land in your CRM, Slack, or automation platform seconds after they finish talking, with everything your team needs to act. Every request is signed so you can trust what arrives.

The webhook contract is v1 and additive-only: fields may be added over time, but nothing documented here will be renamed, retyped, or removed without a new, explicitly announced version.

Quick start

  1. Open Settings → Workspace → Webhook (/app/settings/workspace). Enable the workspace webhook and pick which verdicts you want delivered.
  2. Paste your endpoint URL. A Zapier "Catch Raw Hook", a Make "Webhook", or an n8n "Webhook" trigger URL works out of the box — no native integration needed.
  3. Click Send test event. LeadPass sends a sample lead.pass payload with "test": true so you can see the exact shape.
  4. Map the fields you need (verdict, lead.email, summary, criteria, …) in your receiving tool. Filter on event, verdict, or test as needed.
  5. Copy the signing secret from the same Settings tab and verify signatures in your receiver (see "Verifying the signature" below). Recommended for anything beyond a no-code catch hook.

Events

Event When it fires
lead.pass The lead's first decided verdict is Pass (revision: 1).
lead.review The lead's first decided verdict is Review (revision: 1).
lead.no_pass The lead's first decided verdict is No pass (revision: 1).
lead.verdict_revised Any later decided revision on the same lead (revision > 1): a team-member decision or a re-analysis, even when the verdict value stays the same.

The verdict vocabulary in payloads is pass | review | no_pass. A fourth value, incomplete, only ever appears as previous_verdict or as a re-analysis replacement — it is never subscribable and never a first event.

Subscription filter

The verdict checkboxes in Workspace settings decide what gets delivered:

Payload

Every delivery is a JSON POST body shaped like this:

{
  "id": "evt_01J8ZKQ2V5X6C7...",
  "event": "lead.review",
  "version": 1,
  "created_at": "2026-07-10T12:34:56+00:00",
  "test": false,
  "flow": { "public_id": "fl_8k2m9x", "name": "Before a demo", "language": "es" },
  "lead": {
    "uuid": "9b2f6c9e-...",
    "name": "María García",
    "email": "[email protected]",
    "phone": "+34 600 000 000",
    "company": "Example SL",
    "utm": { "utm_source": "meta" },
    "started_at": "2026-07-10T12:30:00+00:00",
    "completed_at": "2026-07-10T12:34:55+00:00"
  },
  "verdict": "review",
  "previous_verdict": null,
  "decided_by": "system",
  "revision": 1,
  "summary": "Budget named, timing unclear...",
  "criteria": [
    { "key": "budget", "label": "Budget", "polarity": "positive", "band": "high", "evidence": "\"around €2,000 a month\"" }
  ],
  "failed_gates": [],
  "missing_criteria": [],
  "links": { "lead": "https://useleadpass.com/app/leads/9b2f6c9e-..." }
}

Field notes:

Reading criteria bands through polarity

Each criterion's band is one of no_data | very_low | low | medium | high | very_high. Always read the band through the criterion's polarity:

Polarity High band means Low band means
positive Favourable Unfavourable
risk Adverse Favourable

no_data means the lead gave no evidence for that criterion.

Ordering: last write wins by revision

revision increases by 1 for every decided verdict of a lead. Deliveries can retry out of order, so keep the highest revision you have seen per lead.uuid and ignore anything lower. LeadPass also cancels superseded queued deliveries on its side, but the revision rule is the guarantee. A later revision can legitimately carry the same verdict after re-analysis.

Deduplicate by id: it is stable across retries of the same event.

Verifying the signature

Every request carries these headers:

X-LeadPass-Signature: t=1760096096,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e163747325f37d9fa8c2fe...
X-LeadPass-Event: lead.review
X-LeadPass-Delivery: evt_01J8ZKQ2V5X6C7...

v1 is HMAC-SHA256(t + "." + rawBody, secret), computed with your workspace's signing secret (Settings → Workspace → Webhook). To verify:

  1. Parse t (a Unix timestamp) and v1 from the X-LeadPass-Signature header.
  2. Compute HMAC-SHA256 over the string t + "." + rawBody using the raw, unparsed request body.
  3. Compare against v1 with a constant-time comparison.
  4. Reject stale timestamps — ±5 minutes is a sane window.

PHP

[$t, $v1] = [null, null];
foreach (explode(',', $request->header('X-LeadPass-Signature')) as $part) {
    [$k, $v] = explode('=', $part, 2);
    if ($k === 't') { $t = $v; }
    if ($k === 'v1') { $v1 = $v; }
}
$expected = hash_hmac('sha256', $t.'.'.$request->getContent(), $secret);
abort_unless(hash_equals($expected, (string) $v1) && abs(time() - (int) $t) < 300, 403);

Node.js

const crypto = require('crypto');

function verify(signatureHeader, rawBody, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((p) => p.split('=', 2))
  );
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  const valid =
    expected.length === (parts.v1 || '').length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return valid && fresh;
}

Make sure your framework gives you the raw request body for the HMAC — a re-serialized JSON object will not match.

Regenerating the secret (same Settings tab) applies to the very next delivery attempt — update your receiver first.

Delivery and retries

Auto-disable after repeated failures

After 10 consecutive failed real deliveries with no success in between, LeadPass switches the webhook off, cancels anything still queued, and emails the Workspace creator if still an Account member, otherwise the Account owner. Verdicts decided while the webhook is off are not re-sent later. To recover: fix your receiver, re-enable the webhook in Workspace settings, and confirm with Send test event. Test pings never count toward the failure counter.

Test events

The Send test event button in Settings → Workspace → Webhook sends a sample payload with:

Test events never describe a real person and never count toward the auto-disable failure counter. Use them to wire up field mapping and to confirm a receiver is healthy after re-enabling. They keep the exact real-event shape, including a synthetic links.lead string, so no-code tools can discover every field without exposing a stored record.