Lead webhook
Owners and admins with a verified email. Organization settings → Integrations → Lead webhook sends every new contact from a contact form to one address, such as a Zapier, Make or n8n trigger or your own server. Contacts also stay in Analytics → Contacts. With Also send recipient link opens and completions, it also reports recipient links.
The address
The address must use HTTPS on the default port and be reachable from the internet. Addresses that resolve to a private network are refused, and redirects are not followed, so enter the final address. Unticking Send new contacts pauses sending without removing the address; contacts submitted meanwhile are not sent later, and retries already waiting resume when you tick it again.
Send test event posts a sample contact once, without retries, and shows the result under Recent deliveries.
What is sent
Each new contact is posted as JSON:
{
"id": "6c0a4f8e-…",
"event": "lead.created",
"createdAt": "2026-10-25T12:00:00Z",
"organization": { "id": "…", "name": "Northstar Studio" },
"demo": { "id": "…", "title": "Approve a release", "url": "https://demo-creator.com/demo/…" },
"lead": {
"id": "…",
"name": "Grace Hopper",
"email": "grace@example.com",
"screen": "start",
"language": "en"
}
}
screen is start or end, depending on which screen's form was sent, and language is the demo language the viewer had selected, or empty. Test events have "event": "webhook.test", "demo": null and a sample contact. Each request also carries the headers X-Demo-Creator-Event, X-Demo-Creator-Delivery (the same value as id) and X-Demo-Creator-Signature.
Recipient link events, recipient.opened and recipient.completed, are sent once per link and carry a recipient object instead of lead:
{
"event": "recipient.opened",
"recipient": {
"id": "…",
"name": "Grace Hopper",
"email": "grace@example.com",
"firstViewedAt": "2026-10-25T12:00:00Z",
"views": 1,
"completedAt": null
}
}
Check the signature
X-Demo-Creator-Signature looks like t=1792931200,v1=5f2b…. Compute an HMAC-SHA256 of the timestamp, a period and the raw request body, keyed with the Signing secret, and compare it with v1 in constant time. Reject requests whose timestamp is more than five minutes old.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')));
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return (
fresh &&
v1?.length === expected.length &&
timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
);
}
Rotate secret signs requests with a new secret immediately, so update your receiver right after.
Delivery and retries
A response with a 2xx status counts as delivered. Any other status, no response within 10 seconds, or no connection is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours; after that the delivery shows as failed. A retry after a lost response can repeat a contact, so use id to ignore duplicates.
A contact whose demo is moved to trash before delivery is not sent, and Remove webhook cancels deliveries that are still waiting. The 20 latest deliveries are listed; finished deliveries are removed after 30 days.