Webhooks: outbound events with a persisted, retried delivery queue#82
Merged
Conversation
Register URLs to receive data.* / file.* events as signed HTTP POSTs, with
durable at-least-once delivery — the headline integration primitive.
- Management API (key-gated, per-project): POST/GET /v1/webhooks,
GET/PATCH/DELETE /v1/webhooks/:id, POST :id/rotate-secret, and
GET :id/deliveries (recent attempts + status). create/get/rotate return the
signing secret; list omits it. URL must be http(s); events validated.
- Event taxonomy is resource.action (data.created/updated/deleted,
file.created/deleted), matched by exact type, prefix (data.*), or * — and an
optional per-collection filter. Extensible without breaking subscribers.
- Persistence: two tables (webhooks, webhook_deliveries) on both backends. On a
write, matching active hooks are enqueued as durable delivery rows (active-hook
list cached briefly so the no-webhook common case stays off the DB). A
background dispatcher claims due rows atomically (multi-process safe), POSTs
with a 10s timeout, and retries non-2xx/errors with exponential backoff up to
WEBHOOK_MAX_ATTEMPTS, recording status/last error. Delivery is best-effort from
the writer's side: a webhook failure never blocks the originating write.
- Signing: X-Zero-Signature: sha256=HMAC(secret, "<X-Zero-Timestamp>.<body>"),
plus X-Zero-Event / X-Zero-Delivery headers. Stable { id, event, createdAt,
project, data } payload envelope.
- SDK: client.webhooks.{create,list,get,update,rotateSecret,remove,deliveries};
discovery advertises events + signing. Config: WEBHOOK_MAX_ATTEMPTS / _TIMEOUT_MS.
Verification: 69/69 SQLite + 51/51 Postgres 16 — signed delivery + signature
verification + delivery history (delivered), retry-then-failed at the cap, and
URL/event validation. Docs updated (README, /docs, .env.example).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Outbound webhooks with a persisted, retried delivery queue — the integration primitive, designed to be stable so subscribers don't have to relearn it. Per your steer: durable delivery +
data.*/file.*events, HMAC-signed.API (stable surface)
Management is key-gated and per-project:
POST /v1/webhooks{ url, events[], collections?, secret?, active? }→ returns the hook + signingsecret(shown once)GET /v1/webhooks(secrets omitted) ·GET/PATCH/DELETE /v1/webhooks/:id·POST /v1/webhooks/:id/rotate-secretGET /v1/webhooks/:id/deliveries— recent attempts with status / last error / status codeEvents use a
resource.actiontaxonomy —data.created·data.updated·data.deleted·file.created·file.deleted— subscribed by exact type, prefix (data.*), or*, with an optionalcollectionsfilter. New event types can be added later without breaking existing subscribers.Delivery: each is a JSON POST
{ id, event, createdAt, project, data }withX-Zero-Event/X-Zero-Delivery/X-Zero-Timestamp/X-Zero-Signature: sha256=HMAC(secret, "<timestamp>.<body>").How
webhooks,webhook_deliveries) on both SQLite and Postgres. On a write, matching active hooks are enqueued as durable delivery rows (the active-hook list is cached briefly, so the no-webhook common case stays off the DB).WEBHOOK_MAX_ATTEMPTS, recording status. Enqueue is best-effort — a webhook failure never blocks the originating data/file write. The delivery row snapshots url+secret, so editing/deleting a hook can't orphan in-flight deliveries.client.webhooks.{create,list,get,update,rotateSecret,remove,deliveries}; discovery (/v1) advertises events + the signing scheme. Config:WEBHOOK_MAX_ATTEMPTS,WEBHOOK_TIMEOUT_MS.Verification
data.createdevent signed + delivered, signature re-verified in the test, delivery recorded asdelivered; a failing (500) endpoint retried then markedfailedat the cap withlastStatusCode; and URL/event validation400s. (Webhook tests live inapi.test.ts, so they run on both backends — exercising the portableboolean activehandling.)/docs(new Webhooks REST section),.env.example.Next: per-project AI provider config, then verifiable identity (JWT/OIDC).
https://claude.ai/code/session_018efxvWw3MRjdtvE5xgBqya
Generated by Claude Code