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.

Timeline: a record changes, the event dispatches shortly after β€” longer during busy periods β€” then OnePageCRM POSTs to your endpoint, which should respond promptly; a 2xx means delivered, a timeout or error is not retried β€” reconcile missed events with a periodic polling sweep

Timing

BehaviorValue
Dispatch after a changeUsually within seconds β€” can lag behind during busy periods
Response window for your endpointRespond promptly; slow responses count as failed
Retries on failureNone

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:

  1. Read the body.
  2. Verify the secret.
  3. Queue the work.
  4. Return 200 immediately.

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.