Back to help

Webhooks

Webhooks send an HTTP POST to a URL you choose when something happens in your demos. Use them to add leads to your CRM, post to Slack, or start any automation.

Add an endpoint

Go to Settings → Webhooks, paste an https:// URL (for example a Zapier Catch Hook URL) and pick the events it should receive. You can add up to 5 endpoints. Press Send test to post a sample event so you can map its fields before a real one arrives.

Events

Event When
lead.created A viewer submits a demo's lead form.
demo.completed A viewer reaches the end of a demo (once per viewing session).
demo.cta_clicked A viewer clicks the demo's call-to-action (once per viewing session).
demo.published You publish a demo, or publish it again.

Each event is sent once. If your endpoint is down or answers with an error, that event is not retried. The last attempt's result shows in Settings, and every lead stays on the demo's Leads tab.

Payload

{
  "id": "evt_V1StGXR8_Z5j",
  "type": "lead.created",
  "createdAt": "2026-10-04T12:00:00.000Z",
  "test": false,
  "data": {
    "demo": { "id": "…", "title": "Onboarding tour", "slug": "abcdefghjk", "url": "https://demomyproduct.com/d/abcdefghjk" },
    "lead": { "id": "…", "name": "Ada Lovelace", "email": "ada@example.com", "company": "Analytical Engines", "extra": {}, "referrer": "https://example.com", "createdAt": "…" }
  }
}
  • demo.completed and demo.cta_clicked carry demo, viewerId, sessionId, referrer and lead (the viewer's lead if they already filled in the form, otherwise null).
  • demo.published carries demo, version and firstPublish.
  • test is true for events sent with Send test.

Verify the signature

Every request has a DMP-Signature header like t=1759579200,v1=5257a8…. v1 is the hex HMAC-SHA256 of t, a dot and the raw request body, keyed with the endpoint's signing secret (Settings → Webhooks → Reveal).

import crypto from 'node:crypto'

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

Requests also carry DMP-Event (the event type) and DMP-Delivery (the event id, the same for every endpoint, so you can ignore duplicates).