Webhooks
HMAC-signed lead-capture events delivered to your endpoint with automatic retries.
Webhooks are how SendClaw pushes leads into your CRM, marketing automation, or in-house pipeline. Every gate submission fires a lead.captured event; partial submissions optionally fire lead.partial.
Configure a destination
/g/integrations → Add integration → Generic card.
| Field | Notes |
|---|---|
| Name | For your reference (shown in the integrations list). |
| URL | HTTPS only. A check constraint at the database level enforces this. |
| Secret | Auto-generated. Used to sign the payload (see below). Click to copy. |
| Active | Toggle to pause without deleting. |
| Events | Default ['lead.captured']. Add lead.partial to receive abandoned-step events too. |
Then assign the webhook to one or more documents:
- Per-document: Design canvas → Route step → Webhooks card.
- Or do it from the integrations list view.
Payload shape
Version 1 of the payload, with additive-only changes from here. POST with Content-Type: application/json.
{
"id": "evt_01HQ2K3...",
"type": "lead.captured",
"created_at": "2026-05-26T12:34:56.789Z",
"organisation_id": "org_2k4...",
"document": {
"id": "doc_01HQ...",
"name": "AI pricing calculator",
"type": "artifact"
},
"share_link": {
"id": "lnk_01HQ...",
"slug": "ai-pricing-calc",
"utm_source": "twitter",
"utm_medium": "social",
"utm_campaign": "launch",
"utm_content": null,
"utm_term": null
},
"lead": {
"id": "lead_01HQ...",
"email": "jane@example.com",
"phone": "+447700900123",
"first_name": "Jane",
"last_name": "Doe",
"company": "Example Co",
"custom": null,
"status": "complete",
"step_reached": 2,
"captured_at": "2026-05-26T12:34:56.700Z",
"completed_at": "2026-05-26T12:34:56.700Z"
},
"meta": {
"ip": "203.0.113.42",
"user_agent": "Mozilla/5.0 ...",
"referrer": "https://t.co/abc123"
}
}The lead.partial payload has the same shape but status: "partial", step_reached: 1, no completed_at, and a started_at field. Phone-only leads have email: null (with phone present) and vice versa.
Verifying the signature
Every request carries an X-SendClaw-Signature header in the Stripe-style format:
t=1716732896,v1=4f...e2tis the request timestamp (seconds since epoch).v1isHMAC-SHA-256(secret, "t=<timestamp>.<raw-body>")hex-encoded.
To verify in Node:
import crypto from "node:crypto";
function verifyWebhook(rawBody: string, header: string, secret: string) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=") as [string, string]),
);
const expected = crypto
.createHmac("sha256", secret)
.update(`t=${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1),
);
}Reject any request where the signature doesn't validate, or where the timestamp is more than a few minutes old (replay protection).
Retries
If your endpoint doesn't return a 2xx within 8 seconds, SendClaw retries with this backoff:
| Attempt | Delay |
|---|---|
| 1 | immediate |
| 2 | 1 second |
| 3 | 5 seconds |
| 4 | 30 seconds |
After four failed attempts the delivery is marked failed and shows up in the deliveries log on the integration's detail page. Each attempt logs status code and error message.
The total budget fits comfortably inside Vercel Hobby's 60s function limit, all in a single Next 16 after() invocation. No Inngest, no queue.
Deliveries log
/g/integrations/[id] shows the recent deliveries with status, attempt count, response code, and the timestamp. Click into a row for the full request/response details.