Lead delivery

The webhook contract

LeadButton can deliver every lead to your own infrastructure via HTTPS webhooks — Zapier, Make, n8n, or any endpoint you run. Webhook delivery is available on the Pro and Agency plans (email delivery is available on every plan).

Setup

  • Open your space → Destinations.
  • Add a destination, set the type to Webhook, and enter your HTTPS endpoint URL.
  • Save — LeadButton generates a signing secret for the destination (shown on the saved row).
  • Click Send test payload to receive a sample request and wire up your field mapping.

Every active destination receives each lead, so you can combine email and webhook destinations freely.

The request

Leads are delivered as an HTTP POST to your URL:

POST
POST <your webhook url>
Content-Type: application/json
User-Agent: LeadButton-Webhook/1.0
X-LeadButton-Signature: sha256=<hmac-sha256-hex>

The payload is flat JSON. fields is a flat object of the submitted form fields (all values are strings); widget_key, page_url and referrer may be null.

payload
{
  "lead_id": "3e9a4a1e-7a44-4a1e-9a63-1f2ab33c9d10",
  "space_code": "66a3ba5433a10",
  "widget_key": "contactbot",
  "fields": {
    "name": "Ada Lovelace",
    "email": "ada@example.com",
    "message": "I'd like more information."
  },
  "page_url": "https://example.com/contact",
  "referrer": "https://www.google.com/",
  "created_at": "2026-07-07T12:00:00.000Z"
}

Test payloads (from the Send test payload button) carry one extra key, "test": true. Real leads never include it — filter on it if your receiver should ignore tests.

Verifying the signature

Every request is signed. The X-LeadButton-Signature header is sha256= followed by the lowercase hex HMAC-SHA256 of the raw request body bytes, keyed with the destination's signing secret. Always verify against the raw body — parsing and re-serializing the JSON can change the bytes.

node.js
const { createHmac, timingSafeEqual } = require('node:crypto');

function verify(rawBody /* Buffer or string */, signatureHeader, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  return (
    signatureHeader.length === expected.length &&
    timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected))
  );
}

Worked example (also pinned as the cross-runtime test vector in the LeadButton codebase):

test vector
secret:    lb_test_secret
body:      {"id":"7f9c24e5-2c31-4a4b-99d6-99d09d4c8a3b","space":"testspace","widget_key":"contactbot","fields":{"name":"Ada"},"page_url":"https://example.com/","referrer":null,"created_at":"2026-01-01T00:00:00.000Z"}
signature: sha256=b6d180e7fc6a11bae18b2e806465548e3b9d8480553aac0b16338566d767955d

Delivery semantics

  • Timeout: 5 seconds per attempt.
  • Retries: one retry (after ~1 s) on timeout, network errors, 429, and 5xx responses. Other 4xx responses are treated as permanent rejections and are not retried.
  • At-least-once: because of the retry, your endpoint may receive the same lead twice — deduplicate on lead_id if that matters to you.
  • Acknowledge fast: any 2xx response counts as delivered; respond before doing slow work. Redirects are not followed and the response body is ignored.
  • URL requirements: https:// only; private hosts/addresses (localhost, RFC1918 ranges, .local/.internal, link-local) are rejected.

Zapier / Make

  • Zapier: use the Webhooks by Zapier trigger with Catch Hook; paste the generated Catch Hook URL as your destination URL, then Send test payload.
  • Make (Integromat): add a Webhooks → Custom webhook module, copy its address as the destination URL, click Redetermine data structure, then Send test payload.

Neither platform verifies signatures by default; the payload still carries one if you want to check it in a code step.

Deliver your leads wherever you want

Build your flow from a template and paste one snippet. Free up to 20 leads/mo, no credit card.