إنتقل إلى المحتوى الرئيسي

Webhooks

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 (importantly) webhook deliveries don't count against your monthly request quota.

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. We POST to it every time an order is created. Latency is ~2-5 seconds. Zero polling. Zero quota waste.

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

Order/event happens on origin


INSERT row in api_v1_webhook_deliveries


Cron drains every minute (next_retry_at <= NOW())


HMAC-sign body ─────► POST your URL with 10 s timeout
│ │
│ ├─ 2xx → mark delivered, done
│ ├─ 5xx → retry: 1m, 5m, 30m, 2h, 12h
│ ├─ 4xx → ack as poison; do NOT retry
│ └─ no response (timeout / DNS) → retry

└─ 5 failures → move to dead-letter queue (DLQ)

Retry schedule

After each failed attempt, next_retry_at is set to now() + delay[attempt]:

AttemptDelay
11 minute
25 minutes
330 minutes
42 hours
512 hours
6th attemptDLQ — manual intervention

Total retry window is ~14.5 hours. After that the delivery is dead-lettered and your webhook's failure_count increments. Five consecutive dead letters auto-disable the webhook (status changes to dead); contact support to reset.

What counts as a "success"

  • HTTP 200, 201, 202, 204 (any 2xx) — success.
  • HTTP 4xx (400, 401, 403, 404, 422 …) — not retried. We treat 4xx as "your endpoint definitively rejects this payload"; further retries won't help. Fix your endpoint and re-test via POST /v1/webhooks/{id}/test.
  • HTTP 5xx, timeout, DNS failure, TLS failure — retried.

If you want us to retry on a specific 4xx (rare), respond with 503 Service Unavailable instead of 4xx.

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

Signature

X-DZ-Signature = hex( hmac_sha256( WEBHOOK_SECRET, X-DZ-Timestamp + "." + sha256(body) ) )

The WEBHOOK_SECRET is what you got back when you registered the webhook (POST /v1/webhooks). We never expose it again — store it.

What you must do

  1. Verify the signature on every request (see Verifying signatures).
  2. Check the timestamp is within 5 minutes of your server's clock — rejects replays.
  3. Use raw body bytes for hashing — don't re-serialize the JSON.
  4. Be idempotentX-DZ-Delivery-Id is unique per attempt, but the same logical event MAY be delivered more than once (network blips, retries-after-success). Dedupe on your side using delivery_id or 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 IP-lockout. We send from CF Workers — the source IP is in CF's ranges. If you want to whitelist, use Cloudflare's IP list.

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": "abc123def456"
}
FieldNotes
eventThe event type (full list in Event catalog).
store_idYour store id — useful if you have multiple webhooks pointed at the same handler.
occurred_atWhen the event happened in our system, ISO 8601 with TZ.
dataEvent-specific payload. See Event catalog for each event's shape.
delivery_idUnique per attempt. Use this for idempotency on your side.

Quota

Every delivery attempt — success, 4xx, retry — counts as 1 against webhooks_per_month. A single event that retries 5 times costs 5. So:

  • Keep your endpoint reliable (returns 2xx fast) → 1 attempt per event.
  • Failures cost more → fix bugs quickly to avoid burning quota.

What's next