feat(webhooks): admin replay for failed/dead-letter deliveries
Outbound webhook deliveries already retry with backoff, dead-letter after maxAttempts, and notify super admins. This adds operator-level replay: a per-row button on the deliveries log spawns a fresh pending delivery + queues a new BullMQ job. The original failed row stays intact so the response body remains for audit; the replay payload carries retried_from/retried_at markers so receivers can deduplicate. Inbound idempotency was already handled via the documentEvents signatureHash unique index. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { withAuth, withPermission } from '@/lib/api/helpers';
|
||||
import { redeliverWebhookDelivery } from '@/lib/services/webhooks.service';
|
||||
import { errorResponse, NotFoundError } from '@/lib/errors';
|
||||
|
||||
/**
|
||||
* Admin replay for a previously failed/dead-letter webhook delivery.
|
||||
* Spawns a fresh `pending` row + enqueues a new BullMQ job so the
|
||||
* original delivery's failure response is preserved for audit while
|
||||
* the replay flows through the standard worker (HMAC-signed, SSRF
|
||||
* gated, dead-lettered after max retries).
|
||||
*/
|
||||
export const POST = withAuth(
|
||||
withPermission('admin', 'manage_webhooks', async (_req, ctx, params) => {
|
||||
try {
|
||||
const { webhookId, deliveryId } = params;
|
||||
if (!webhookId || !deliveryId) throw new NotFoundError('Delivery');
|
||||
const result = await redeliverWebhookDelivery(ctx.portId, webhookId, deliveryId, {
|
||||
userId: ctx.userId,
|
||||
portId: ctx.portId,
|
||||
ipAddress: ctx.ipAddress,
|
||||
userAgent: ctx.userAgent,
|
||||
});
|
||||
return NextResponse.json({ data: result });
|
||||
} catch (error) {
|
||||
return errorResponse(error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
Reference in New Issue
Block a user