Webhooks
Delivery
When webhooks arrive, how fast to respond, at-most-once semantics, and how to build a receiver around them.
Last updated Jul 14, 2026
Knowing exactly how delivery works is what separates a receiver that holds up in production from one that silently drifts out of sync. Here are the rules.
Timing
| Behavior | Value |
|---|---|
| Dispatch after a change | Usually within seconds β can lag behind during busy periods |
| Response window for your endpoint | Respond promptly; slow responses count as failed |
| Retries on failure | None |
Dispatch timing
Events are usually dispatched within seconds of the change β but there is no guaranteed upper bound. Deliveries go through a queue, and during busy periods or maintenance that queue can back up, so an event may arrive minutes (occasionally longer) after the change it describes. There is no batching either way: three edits in quick succession produce three separate deliveries.
Donβt build anything that assumes prompt delivery. For a sync dashboard or a Zapier-style automation, the usual latency is invisible. For anything that needs a hard real-time guarantee, webhooks are the wrong tool.
Respond fast
Slow responses count as failed deliveries β OnePageCRM wonβt wait on your endpoint indefinitely. The safe pattern:
- Read the body.
- Verify the secret.
- Queue the work.
- Return
200immediately.
Do the actual processing β API calls, database writes, downstream notifications β after youβve responded, not before.
Delivery is at-most-once
If a delivery fails β your endpoint returns a non-2xx status, times out, or the connection drops β it is not retried. The event is gone. The response status is not used to re-send anything.
Build for it
Webhooks are a freshness signal, not a transaction log.
Treat webhooks as βgo check nowβ
The reliable pattern is to use the event as a trigger, not as the
source of truth. When a contact / updated event arrives, you can
trust the payload β but when an event doesnβt arrive, you know
nothing.
Reconcile with polling
For any sync where missing a change matters, pair webhooks with a
periodic reconciliation poll against the
REST API: fetch the records you mirror, compare
modified_at against your copy, and repair the differences. Run it
hourly or daily depending on how much drift you can tolerate. The
poll catches whatever webhooks missed β failed deliveries, downtime
on your side, and imports, which never fire webhooks.
Be idempotent
There is no dedupe on the sending side, and rapid consecutive edits each produce an event. Make your processing idempotent so handling the same logical change twice is harmless. A practical dedupe key:
type + ":" + entityId + ":" + timestamp
where entityId is data.<type>.id (data.contact.id,
data.deal.id, β¦) β or data.id for deleted events, where
data is flat.
Skip events whose key youβve already processed. The tutorial shows this in a working receiver.
Expect bursts
Bulk operations fire one event per affected record. A bulk tag update on 500 contacts is 500 POSTs to your endpoint. Queue first, process later β and make sure your queue absorbs spikes without slowing your responses down.