Skip to content

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 side
from d2b.client import _Webhooks
ok = _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-Timestamp is 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-Delivery is 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 deliveries but is not retried again. Each event produces one delivery, but an attempt whose response never arrived is retried, so the same delivery (the same X-D2B-Delivery) can reach you more than once: process deliveries idempotently, keyed by X-D2B-Delivery. The payload’s timestamp is 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.