Webhooks
hook = client.webhooks.create("https://example.com/hook", events=["artifact.updated", "job.completed"])secret = hook["secret"] # available only in this response
# On the receiving sidefrom d2b.client import _Webhooksok = _Webhooks.verify_delivery(secret, request.headers, raw_body) # signature + send time (default: within 5 min)- Signature:
X-D2B-Signature-V2: sha256=<hex>= HMAC-SHA256(secret,"{X-D2B-Delivery}.{X-D2B-Timestamp}." + raw_body).X-D2B-Timestampis the send time of that attempt (UNIX seconds, refreshed on every retry): reject a request whose age exceeds your tolerance (for example 5 minutes) to defeat replays.X-D2B-Deliveryis the delivery id (the same on every retry). Verify against the raw body (do not re-serialise) - The legacy
X-D2B-Signature: sha256=<hex>= HMAC-SHA256(secret, raw_body) is still sent. It carries no send time, so it cannot stop a replay - Events:
artifact.updated/artifact.deleted/artifact.stale/snapshot.committed/source.analyzed/source.materialized/conflict.created/version.branched/version.merged/job.completed - Delivery history:
GET /api/v1/me/webhooks/{id}/deliveries - Retries: non-2xx responses and connection failures are retried up to 14 times with exponential backoff from 30 s to 1 h (about 8 hours in total). A delivery that still fails stays visible under
deliveriesbut is not retried again. Each event produces one delivery, but an attempt whose response never arrived is retried, so the same delivery (the sameX-D2B-Delivery) can reach you more than once: process deliveries idempotently, keyed byX-D2B-Delivery. The payload’stimestampis the event’s time and does not change on retry
For long operations (big ingests), prefer async=true + waiting on job.completed over polling — cheaper, and friendlier to agent timeouts.