Help Center

What a lead webhook destination must be

The rules every destination follows, what your receiver has to do, and how to read the most common delivery failures.

Lead webhooks send each new lead to one address you choose. That address has to meet a few rules, and the most common problems all come from breaking one of them. This guide collects the rules in one place, so the Zapier, Make and n8n guides can stay short.

Use this guide when

You are choosing the address that receives your leads, you want to know what your receiver has to answer, or a delivery failed and you want to know why.

Before you start

  • Professional includes lead webhooks. In your Personal workspace, open Lead webhooks to configure your first destination. If a previously revoked destination is blocked, contact support to restore access.
  • Have the webhook address of the tool you want to use at hand. Each tool has its own way to create one.
  • Keep the signing secret private. Treat it like a password.

Steps

  1. Open Account, then Lead webhooks. The page shows whether a destination is saved and lets you configure your first destination.
  2. Choose an address that starts with https, uses port 443 and sits on a public hostname. An IP address is not accepted, and neither is a username or password inside the address, or a part after a # sign.
  3. Make sure the address is the final one. Zapped never follows a redirect, so a redirect counts as a failed delivery. This is why a Google Apps Script web app cannot be a destination, because it answers with a redirect. Use the address your automation tool gives you instead.
  4. In Configure destination, paste the address, tick the confirmation and choose Save destination.
  5. Copy the signing secret that is shown once. It starts with whsec_ and cannot be shown again, so paste it into your tool straight away.
  6. Open Test history and request one test. No real lead is created, and the request is signed exactly like a real one.
  7. Check the result in Test history, then open Lead history to watch real leads arrive. Times are in UTC.
  8. Replace the address or the secret only when you mean to. Replacing the address always issues a new signing secret, and the tool needs the new secret before the next lead arrives.
  9. For the complete request reference, open Lead webhooks in the API documentation. It lists every header, every field, the retry table and sample verification code.

One destination per account. Saving a new address replaces the old one and creates a new signing secret, and sends already waiting from the old setup are cancelled.

What your receiver has to do

Your address receives one JSON POST per lead. The receiver answers, and the answer decides what happens next.

  • Answer 2xx within 5 seconds: connecting may take up to 2 seconds and the whole request up to 5 seconds. A slow answer is treated as a failure.
  • Answer quickly, then work: if your tool accepts the request first and processes it afterwards, slow work never causes a retry.
  • Retries: 408, 429 and 5xx: these come back automatically with growing gaps. A Retry-After header is respected.
  • Not retried: other 4xx: a 400 or a 404 means the attempt sequence has ended. You can send the same event again from Lead history.
  • Not retried: a redirect: redirects are never followed. Give the final address.
  • Small bodies: a request body is at most 128 KB. A large form with many answers still fits well inside that.

What arrives, and how to trust it

Every request carries three headers: webhook-id, webhook-timestamp and webhook-signature. The body holds the event id, the event type, a schema version, the capture time and the lead data.

The signature follows the Standard Webhooks format. To check it, remove whsec_ from your signing secret and base64 decode the rest. Then compute an HMAC SHA-256 value over webhook-id, a dot, webhook-timestamp, a dot and the raw body. The result, written in base64 with v1, in front, must match the signature header. Your tool has to hash the body exactly as it arrived, before any parsing, otherwise the value will not match.

Repeats, retries and order

A retry or a replay keeps the same webhook-id and the same body, with a fresh webhook-timestamp. Treat the event id as the identity of the lead event, and drop an event id you have already handled.

Deliveries can also arrive out of order, so do not rely on arrival time. Sort by occurred_at when the order matters, and keep the event id to recognise a repeat.

Only one kind of event is sent, lead.created. A test uses the type webhook.test and carries a synthetic marker instead.

Where the sample comes from

A test event carries the same data structure as a real lead, so you can build your field mapping from it. Its values are invented and every identifier is null: data.lead.id, data.lead.duplicate_of_lead_id, data.card.id and data.card.block_id. Because those ids are null, any rule you build on a card id will treat a test as a stranger to that card. Route tests to a test path and never let one become a real contact.

When your mapping is ready, capture one lead through your own published card and watch it arrive. That is the only way to confirm the whole path, from the form to your tool to the record.

Before any marketing step

A visitor may send a message without agreeing to marketing. Check data.consent.marketing_consent_at before you add the contact to a list. When it is null, the visitor did not agree, even when their email address is present.

What Delivered really means

Automation tools answer the request first and do the work afterwards. That is what keeps the answer fast, and it means a delivery can be marked Delivered while the record is still missing, or while your own signature check has rejected the request a moment later.

Lead history is the transport record, not the outcome record. Read the tool's own history whenever you need to know what happened to a lead: Zapier's task history, Make's scenario run history, or n8n's execution list. All three show each run and each step.

A delivery marked Delivered is therefore worth checking after you change a filter. The request arrived, and the tool may still have dropped it on purpose.

For developers

The developer guide at /api-documentation/lead-webhooks documents every header, every payload field, the retry table, the test event and ready made PHP and Node.js verification snippets.

Checking a request in your own Node service

Paste this function into your service, or into a code step in a tool such as Pipedream, and call it with the request headers, the raw body text, your signing secret and the current time in seconds.

The body must be the exact text that arrived. Do not parse it first and do not re-write it, because the signature covers those exact bytes.

const crypto = require('crypto');

function verifyZappedWebhook({ headers, rawBody, signingSecret, nowSeconds }) {
  const key = Buffer.from(String(signingSecret).slice(6), 'base64');
  const id = String(headers['webhook-id'] || '');
  const time = String(headers['webhook-timestamp'] || '');
  const fresh = Math.abs(nowSeconds-Number(time)) <= 300;
  let verified = false;
  if (key.length === 32 && id && time && rawBody && fresh) {
    const expected = crypto.createHmac('sha256', key)
      .update(id + '.' + time + '.' + rawBody)
      .digest();
    verified = String(headers['webhook-signature'] || '').split(' ').some(function (part) {
      if (part.indexOf('v1,') !== 0) return false;
      const given = Buffer.from(part.slice(3), 'base64');
      return given.length === expected.length && crypto.timingSafeEqual(given, expected);
    });
  }
  return { verified: verified, event_id: id };
}

In Pipedream, the HTTP trigger parses a JSON body before your step sees it, so ask Pipedream to save the original body and read it back. Add the query string ?pipedream_upload_body=1 to the endpoint address when you save it in Zapped, and the step reads the untouched bytes from steps.trigger.event.body.raw_body_url. In a code step, the call looks like this, and the raw bytes come from that URL rather than from the parsed body.

Keep the signing secret in an environment variable, never in the code text. Stop the step when verification fails, so nothing writes before the request is trusted.

const headers = steps.trigger.event.headers;
const rawUrl = steps.trigger.event.body.raw_body_url;
const rawBody = await (await fetch(rawUrl)).text();
const result = verifyZappedWebhook({
  headers: headers,
  rawBody: rawBody,
  signingSecret: process.env.ZAPPED_SIGNING_SECRET,
  nowSeconds: Math.floor(Date.now() / 1000),
});
if (!result.verified) {
  await $.flow.exit('Signature rejected');
}

Plans and availability

Lead webhooks are included with Professional. Open Account, then Lead webhooks, and save your HTTPS destination to get started. Delivery begins only after you configure a destination and the service is available. Custom-plan access follows your agreement.

Good ways to use this

  • Use the address your automation tool gives you rather than an address you built by hand. The tool already answers in time and shows you its own history.
  • Keep the signing secret in the tool's credential store, never in a spreadsheet or a note in your inbox.
  • Request one test after every change to the address or the secret, and check Test history before you trust the next real lead.
  • Let a retry finish before you change anything. An entry that says Automatic retry pending is still working.

Common questions

Why can I not use my own website address?

You can, as long as that address is HTTPS on port 443 with a public hostname and answers 2xx within a few seconds. A page that only serves a contact form will not work, because it has to accept a POST, read the signature and answer without a redirect.

What does a 2xx in Lead history prove?

It proves the receiver accepted the request. Tools such as Zapier, Make and n8n answer first and work afterwards, so the record in your CRM may still be missing. Check the tool's own history for the real result.

Can I send leads to two systems at once?

Yes, in effect. Send the webhook to one automation tool and let that tool branch to each system. The account itself has one destination.

Do I have to verify the signature?

Anyone who learns your address can post to it. The signature is what proves a request came from Zapped, so verify it before you create a record, especially when the tool lets a stranger reach your data.

If something looks wrong

  • If the signature does not match, confirm the tool hashes the raw body and uses the secret saved when it was issued. Secrets cannot be shown again. If yours is lost, use Replace signing secret and update the receiver with the new value.
  • If a delivery says Delivered but no record appears, the tool accepted the request and its own history has the answer. Open it and look for the action that failed.
  • If a delivery failed right after a redirect, the destination is not the final address. Open the destination in your browser and enter the address it lands on.
  • If nothing arrives at all, request one test event and check the tool's history for that request. If the tool never saw it, check the address, your plan access and the destination status.
  • If the address is refused when you save it, read the message. It names the rules: HTTPS on port 443, a public hostname rather than an IP address, no username or password, and no redirects.
  • If a delivery says Failed and it is not a retry state, the attempt sequence has ended. Open the entry in Lead history and request one replay.

Was this guide helpful?

Still have questions? Contact support and we'll help you out.