Files
AI_Pharmacy_Api/src/modules/webhooks/service.ts
T

72 lines
2.3 KiB
TypeScript

import crypto from 'crypto';
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'test_secret_for_webhooks';
const WEBHOOK_URL = process.env.WEBHOOK_URL || 'http://localhost:8081/webhooks/pharmacy';
const MAX_RETRIES = 3;
export class WebhookService {
/**
* Fire-and-forget: dispatches the webhook in the background.
* Dispensing will NEVER fail because of a webhook issue.
*/
dispatchEvent(event: string, idempotencyKey: string, payload: any) {
// Run in background — no await, no blocking
this._sendWithRetry(event, idempotencyKey, payload).catch((err) => {
console.error(`[WEBHOOK] All retries failed for ${event}: ${err.message}`);
});
}
/**
* Internal: send webhook with exponential backoff retry.
*/
private async _sendWithRetry(event: string, idempotencyKey: string, payload: any) {
const timestamp = Math.floor(Date.now() / 1000);
const bodyString = JSON.stringify(payload);
const dataToSign = `${timestamp}.${bodyString}`;
const signature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(dataToSign)
.digest('hex');
const signatureHeader = `t=${timestamp},v1=${signature}`;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
console.log(`[WEBHOOK] Attempt ${attempt}/${MAX_RETRIES} — ${event} (${idempotencyKey})`);
const response = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Pharmacy-Event': event,
'X-Pharmacy-Signature': signatureHeader,
'Idempotency-Key': idempotencyKey,
},
body: bodyString,
});
if (response.ok) {
console.log(`[WEBHOOK] ✓ Delivered: ${event}`);
return; // Success — stop retrying
}
console.warn(`[WEBHOOK] Server responded ${response.status} for ${event}`);
} catch (error: any) {
console.error(`[WEBHOOK] Attempt ${attempt} failed: ${error.message}`);
}
// Exponential backoff: 1s, 2s, 4s
if (attempt < MAX_RETRIES) {
const delay = Math.pow(2, attempt - 1) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
console.error(`[WEBHOOK] ✗ Failed after ${MAX_RETRIES} attempts: ${event}`);
}
}
export const webhookService = new WebhookService();