Skip to content

Feat/durable webhook queue - #182

Open
samuel22x wants to merge 5 commits into
determined-001:mainfrom
samuel22x:feat/durable-webhook-queue
Open

Feat/durable webhook queue#182
samuel22x wants to merge 5 commits into
determined-001:mainfrom
samuel22x:feat/durable-webhook-queue

Conversation

@samuel22x

Copy link
Copy Markdown
Contributor

What

Describe what this PR changes.

Why

The motivation / problem being solved. Link any related issue (e.g. Closes #8.7).

How

Brief notes on the approach. If it touches a chain or anchor, confirm the change
stays behind a port and the domain (packages/core) imports no chain SDK.

Checklist

  • pnpm typecheck passes
  • pnpm test passes
  • pnpm build passes
  • Added/updated tests for changed behaviour where practical
  • Did not change the off-ramp from seller_initiated to inline
  • Updated docs (README / API.md) if behaviour or endpoints changed
  • Preserved granular commit history (do not squash into a single blob)

Bug fix? (skip if not a bug fix)

  • Added a regression test that would have caught this bug
  • Added a row to docs/FIXLOG.md (date · symptom · root cause · fix commit · test name)s

closes #22

samuel22x and others added 4 commits July 29, 2026 00:39
Replace in-process retry with a DB-backed webhook_queue table so a
crash mid-backoff no longer loses pending deliveries.

- Add webhook_queue table: id, webhook_id, link_id, event, payload,
  attempts, next_attempt_at, status (pending/claimed/delivered/dead),
  last_status_code, last_error, created_at, updated_at.
  Add idx_webhook_queue_due index on (status, next_attempt_at).
- Extend webhook_deliveries with attempt (1-based) and queue_entry_id
  so every individual attempt is queryable, not just the final outcome.
- Extend WebhookRepository port: enqueue, claimDue, updateQueueEntry,
  findQueueEntry, findWebhookById.
- WebhookSender.dispatch now writes queue rows and returns immediately;
  event emission never blocks a state transition.
- New WebhookWorker polling delivery loop: claims due rows with an
  optimistic-lock UPDATE (prevents double-send across concurrent
  instances), delivers signed frozen payload, reschedules with
  exponential backoff + full jitter, dead-letters after N attempts.
  Records a webhook_deliveries row per attempt.
- Wire WebhookWorker into container start()/stop().
- POST /webhooks/deliveries/:id/replay resets dead/failed entries to
  pending for immediate redelivery; 409 if entry is in-flight.
- Signing scheme and headers (X-Checkout-Signature, X-Checkout-Event)
  identical to old sender — receivers need no changes.
- Update docs/API.md; create ISSUES.md tracking entry.
An 87-line ISSUES.md unrelated to the webhook queue was committed on this
branch. The repository does not track ISSUES.md — the maintainer keeps a much
larger one locally — so this file only shadows theirs on checkout.
claimDue() transitioned pending -> claimed with a conditional UPDATE, then
re-SELECTed rows WHERE status='claimed'. That second query also matches rows a
concurrent worker claimed between our UPDATE and our SELECT, so two instances
could both return — and deliver — the same webhook. Issue 4.2 item 3 requires
the opposite.

Taking the rows off the UPDATE with RETURNING yields exactly the rows this
statement transitioned, with no window in between.
The branch was 105 commits behind. Six conflicts resolved; the substantive one
is webhook-sender.ts, where this branch turns the sender into an enqueuer while
main had grown in-process retry, metrics and an inFlightCount getter.

Carried across from main rather than dropped:

- webhookQueueDepth() fed off sender.inFlightCount, which no longer exists.
  Repointed at a pending counter on the sender, which is what the
  webhook_deliveries_in_flight gauge was always meant to measure.
- WebhookDelivery gained attempt and queueEntryId; queueEntryId is nullable to
  match the column ('null for legacy rows') and listDeliveriesByLinkId now maps
  both. recordDelivery calls carry createdAt.
- Five test fakes gained findWebhookById / enqueue / claimDue /
  updateQueueEntry / findQueueEntry.

Also on this branch: the stray 87-line ISSUES.md is removed (573a832) and the
queue claim is made genuinely atomic with RETURNING (3d679f4).

CI is red and deliberately left that way: 20 pre-existing tests now fail
because the behaviour they assert moved to WebhookWorker. See the review.
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

@determined-001 is attempting to deploy a commit to the determined's projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4.2 - Durable webhook delivery queue

3 participants