---
title: "Delivery"
description: "When webhooks arrive, how fast to respond, at-most-once semantics, and how to build a receiver around them."
canonical_url: https://developer.onepagecrm.com/webhooks/delivery/
source: Markdown mirror of https://developer.onepagecrm.com/webhooks/delivery/
---

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](/assets/diagrams/webhook-delivery.svg)

## 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:

1. Read the body.
2. [Verify the secret](/webhooks/security/).
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](/api/reference/): 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](/webhooks/events/#what-triggers-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:

```text
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](/tutorials/webhooks/) 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.
