Guide · 9 min read
Webhooks & integrations: a practical guide
Signing, retries, idempotency and delivery logs — building integrations that don't silently lose or duplicate data.
Why webhooks beat polling
A webhook POSTs each form submission to a URL you control the instant it happens, instead of you polling an API on a timer and hoping the interval is short enough. It is the difference between reacting in real time and reacting eventually.
For forms this powers the things people actually want from integrations: a new submission in the CRM before the salesperson refreshes, a Slack ping the moment a high-intent lead arrives, an automation kicked off with no human in the loop.
Verify every payload (signing)
An unauthenticated webhook endpoint is a public write API: anyone who discovers the URL can POST fabricated submissions into your systems. Webhook signing closes that door by attaching an HMAC of the raw body, computed with a shared secret, to each request.
Recompute the HMAC over the exact raw bytes you received — before parsing or reformatting — and compare with a constant-time function so an attacker cannot learn the secret from response timing. Include a timestamp in the signed data and reject anything too old to defeat replay attacks.
Survive failure (retries + idempotency)
Receivers go down, time out, and return 500s. A serious webhook sender retries with exponential backoff, which means your endpoint will sometimes receive the same event more than once — that is correct behaviour, not a bug to suppress.
Make the handler idempotent so duplicates are harmless. The standard approach is an idempotency key: the sender includes a unique event id, you record which ids you have already processed, and you ignore repeats. Signing establishes that a payload is authentic; idempotency makes safely retrying it possible. You need both.
Observability: the delivery log
A delivery log recording every attempt — payload, response code, timestamp, retry count — is the difference between "the integration is flaky" and "deliveries to this endpoint have returned 504 since 14:00". Without it you are debugging blind.
Manual replay from the log is the operational escape hatch: when a downstream system was down for an hour, you replay the affected events rather than asking respondents to resubmit. Build replay in from the start; it is painful to retrofit.
Designing the payload
Key answers by stable question id, never by label, so a renamed question does not silently break every consumer of the webhook — the same id-stability discipline that makes stored responses safe long-term.
Include event metadata the receiver needs to be idempotent and to attribute the lead: a unique event id, a timestamp, the form id, and any UTM values captured in hidden fields. Keep the schema stable and version it; downstream systems will couple to its shape whether you intend them to or not.
Failure modes to plan for
The receiver is slow: respond 2xx fast and process asynchronously, so the sender does not time out and retry a request you actually handled.
The receiver is down: rely on signed, logged, retried delivery plus manual replay rather than losing data. And treat webhook data as untrusted input — validate it against your schema on arrival exactly as you would a form submission from a browser, because a signature proves origin, not that the contents are well-formed.