From 3471b8d2ae036a5363b5c2d26d22b7fcda262a27 Mon Sep 17 00:00:00 2001 From: aiirvizionz Date: Fri, 10 Jul 2026 13:53:47 -0600 Subject: [PATCH] Schedule failed webhook retries --- lib/db.ts | 1 + lib/webhooks.ts | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/db.ts b/lib/db.ts index 27f8bf0..bc4ba7d 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -137,6 +137,7 @@ async function initSchema(): Promise { UNIQUE (endpoint_id, idempotency_key) ) `); + await d.execute(`CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_retry ON webhook_deliveries (status, next_attempt_at)`); // ---- webhooks: inbound config + idempotency ledger --------------------- await d.execute(` diff --git a/lib/webhooks.ts b/lib/webhooks.ts index 3c82e09..812b301 100644 --- a/lib/webhooks.ts +++ b/lib/webhooks.ts @@ -138,11 +138,19 @@ async function recordDelivery( ) { await db().execute({ sql: `INSERT INTO webhook_deliveries - (endpoint_id, event_type, payload, idempotency_key, status, attempts, response_status, last_error, delivered_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, CASE WHEN ?='delivered' THEN datetime('now') END) + (endpoint_id, event_type, payload, idempotency_key, status, attempts, response_status, last_error, next_attempt_at, delivered_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, + CASE WHEN ?='failed' THEN datetime('now', '+30 seconds') END, + CASE WHEN ?='delivered' THEN datetime('now') END) ON CONFLICT (endpoint_id, idempotency_key) DO UPDATE SET status=excluded.status, attempts=webhook_deliveries.attempts+1, - response_status=excluded.response_status, last_error=excluded.last_error`, - args: [endpointId, type, body, idem, status, attempts, responseStatus, err, status], + response_status=excluded.response_status, last_error=excluded.last_error, + next_attempt_at=CASE + WHEN excluded.status='failed' + THEN datetime('now', '+' || min(1800, 30 * (1 << min(webhook_deliveries.attempts, 6))) || ' seconds') + ELSE NULL + END, + delivered_at=CASE WHEN excluded.status='delivered' THEN datetime('now') ELSE webhook_deliveries.delivered_at END`, + args: [endpointId, type, body, idem, status, attempts, responseStatus, err, status, status], }); }