Skip to main content

Webhook Retry Logic

If Meridian doesn't receive an HTTP 200 response from your endpoint within 5 seconds, it marks the delivery as failed and retries with exponential backoff.

Retry schedule​

AttemptDelay after previous attempt
1st retry5 minutes
2nd retry30 minutes
3rd retry2 hours
4th retry5 hours
5th retry10 hours
FailedEvent moved to dead-letter queue

After 5 failed attempts (covering roughly 17 hours), the event is placed in the dead-letter queue.

Dead-letter queue​

:::tip New in v2.4 Failed events are now preserved in a dead-letter queue instead of being silently dropped. :::

You can inspect and replay dead-lettered events from the Dashboard (Developers → Webhooks → Dead Letter Queue) or via the API:

# List dead-lettered events
curl https://api.meridian.dev/v1/webhook_endpoints/we_EndpointId/dead_letter \
-u sk_test_4eC39HqL...:

# Replay a single event
curl -X POST \
https://api.meridian.dev/v1/webhook_endpoints/we_EndpointId/dead_letter/evt_EventId/replay \
-u sk_test_4eC39HqL...:

Respond immediately, process async​

The 5-second response window is tight. If your handler does any significant work (database writes, external API calls), respond 200 immediately and process the event in a background queue:

app.post('/webhooks/meridian', express.raw({ type: 'application/json' }), (req, res) => {
const event = verifyAndParseEvent(req);

// Acknowledge immediately so Meridian doesn't retry
res.json({ received: true });

// Process in background
jobQueue.add('process-webhook', { eventId: event.id, eventType: event.type });
});

Handle duplicate deliveries​

An event may be delivered more than once (e.g., if your server acknowledged after the 5-second window). Always make your handlers idempotent — use the event id to deduplicate:

async function handlePaymentSucceeded(event) {
const alreadyProcessed = await db.webhookEvents.findOne({ eventId: event.id });
if (alreadyProcessed) return; // skip duplicate

await db.webhookEvents.insert({ eventId: event.id, processedAt: new Date() });
await fulfillOrder(event.data.object);
}

Manually retry from the Dashboard​

You can trigger a retry for any failed event from Developers → Webhooks → [your endpoint] → Event log. Click the event and select Resend.