Webhooks are real-time push notifications from DZBuild to your server when something happens on your store. Use them instead of polling — less load, lower latency, and webhook deliveries don't count against your monthly request quota.
ℹ️ Info — There are two different DZBuild webhook systems
Pick the right one before you build.
Merchant Webhooks addon | API v1 webhooks (this section) | |
How you set it up |
|
|
Who can use it | Unlimited plan and up | Any store with a pilot-enrolled API key |
Endpoints per store | 1 on Unlimited, 3 on Enterprise | No limit is enforced |
Signature |
| Signed with a key you are not given — merchants cannot verify it today. See Signature. |
Which orders it covers |
| Only orders created or updated through the API |
Delivered from | Cloudflare IP ranges | DZBuild's own egress addresses — ask support for the current list |
Extras | Delivery log UI, secret regeneration, HTTPS-only target validation, auto-disable after 10 consecutive failures | — |
If all you need is reliable order notifications, the addon is the better product. Use API v1 webhooks when your integration already talks to the REST API.
Why webhooks
Compare:
Polling — your code calls GET /v1/orders?since=... every minute. 1440 calls/day, 1440 round-trips, the API quota burns evenly, and the latency from "order created" to "your code knows" is 60 seconds.
Webhooks — you register https://yourapp/webhooks once. Every order created through POST /v1/orders queues a delivery, and the queue drains continuously, so latency is typically under a minute. Zero polling, zero quota waste.
⚠️ Warning — API v1 webhooks only see API traffic
order.created fires only for orders created via POST /v1/orders. Storefront checkouts, landing-page orders and manual dashboard orders fire nothing here. Same for status changes — see the Event catalog.
The only time polling beats webhooks is when: - Your endpoint can't be reached from the internet (then poll from inside your network). - You don't have a server (use polling from a scheduled lambda / cron job).
How delivery works
An API v1 write happens
│
▼
A delivery is queued for every webhook subscribed to that event
│
▼
The queue drains continuously — typically under a minute
│
▼
Signed body ─────► POST your URL (5 s connect, 10 s total)
│
├─ 2xx → mark delivered, done
├─ 5xx → re-queued, retried every minute
├─ 4xx or 3xx → one attempt, no retry
└─ no response (timeout / DNS / TLS) → one attempt, no retry
Retry behaviour
⚠️ Warning — Retries in v1 don't behave like a normal backoff
Read this before you design around retries.
HTTP 5xx — the delivery is re-POSTed once per minute, indefinitely, until your endpoint returns 2xx or you delete the webhook. The interval never grows and the delivery is never given up on, so don't design around a backoff ladder — there isn't one.
Timeout, DNS failure, TLS failure — attempted exactly once, then abandoned. No retry, and the delivery is never picked up again.
HTTP 4xx and 3xx — attempted once, never retried. Redirects are not followed, so a
301/302counts as a failure.
There is no auto-disable. The webhook's failure_count increments once per failed attempt and resets to 0 on any success; status stays active.
Practical consequences:
Return 2xx fast. If you can't process a payload, still return 2xx and drop it — returning 5xx signs you up for a POST every 60 seconds forever.
Don't rely on a retry to cover a slow endpoint. A timeout is a single lost delivery. Persist the body to your own queue and ack immediately.
Reconcile with a poll. Because failures are silently abandoned, run a periodic
GET /v1/orders?since=...sweep as a safety net.
What counts as a "success"
HTTP 200, 201, 202, 204 (any 2xx) — success.
HTTP 4xx (400, 401, 403, 404, 422 …) — not retried. Fix your endpoint and re-test via
POST /v1/webhooks/{id}/test.HTTP 3xx — not retried. We don't follow redirects; point the webhook at the final URL.
Timeout, DNS failure, TLS failure — not retried either. We verify TLS certificates strictly, so a self-signed cert fails here.
HTTP 5xx — retried, but see the loop warning above.
Security model
What we send
Content-Type: application/json User-Agent: dzbuild-webhook/1 X-DZ-Timestamp: <unix seconds> X-DZ-Signature: <hex hmac-sha256> X-DZ-Delivery-Id: <numeric delivery id>
Signature
⚠️ Warning — API v1 signatures are not verifiable by merchants yet
X-DZ-Signature is not derived from the per-webhook secret returned by POST /v1/webhooks, and that secret is never given back to you in a form you could sign with — so verification code written against it rejects 100% of genuine deliveries.
Treat X-DZ-Signature as opaque until per-webhook signing ships. If you need a signature you can actually check, use the merchant Webhooks addon at /dashboard/webhooks, which signs each endpoint with that endpoint's own secret.
What you must do
Re-read before you act. Since the signature isn't verifiable, treat the payload as a notification rather than as authenticated data — fetch the record with
GET /v1/orders/{id}using your API key before you ship, charge, or fulfil anything.Make the URL unguessable. A long random path segment or a shared token in the query string is your practical authentication today.
Check the timestamp is within 5 minutes of your server's clock — cheap replay protection.
Use raw body bytes if you hash anything — don't re-serialize the JSON.
Be idempotent — the same logical event MAY be delivered more than once (5xx re-queues). Dedupe on
delivery_idor on the event's own ids.
What we DON'T do
We don't authenticate outbound with mTLS. If your endpoint requires it, set up a reverse proxy that strips/adds mTLS in front of your handler.
We don't send API v1 deliveries from Cloudflare IP ranges, so allow-listing those blocks every delivery. If you need an IP allow-list, contact support for the current egress addresses — they can change. (The merchant Webhooks addon is the opposite: its deliveries do come from Cloudflare ranges.)
Payload envelope
Every webhook body has the same outer shape:
{
"event": "order.confirmed",
"store_id": 13,
"occurred_at": "2026-04-30T21:18:21+00:00",
"data": { "order_id": 6894, "old_status": "pending", "new_status": "confirmed" },
"delivery_id": "9f2c41ab77e05d18"
}
Field | Notes |
| The event type (full list in Event catalog). |
| Your store id — useful if you have multiple webhooks pointed at the same handler. |
| When the event happened in our system, ISO 8601 with TZ. |
| Event-specific payload. See Event catalog for each event's shape. |
| 16-hex string, unique per delivery (one webhook × one event). It is byte-identical on every retry — that's what makes it usable for dedup. |
The X-DZ-Delivery-Id header is a different value: a numeric delivery id, e.g. 4127. It is also stable across retries, but it does not equal the delivery_id in the body. Dedupe on one of them consistently — don't mix.
Quota
Webhook deliveries are not metered and not capped today. A webhooks_per_month figure is reported by GET /v1/usage and GET /v1/quotas, but nothing increments it and nothing enforces it. Delivery attempts also don't consume your requests_per_month API quota.
That's not a licence to be slow — a 5xx endpoint is re-POSTed every 60 seconds indefinitely (see Retry behaviour).
What's next
Registering —
POST /v1/webhookswith full body, response, examples.Event catalog — every event with sample data payload.
Verifying signatures — code in 4 languages.