Dead letter queues for webhook delivery
What a dead letter queue is for webhook delivery, and when a failed webhook should stop retrying and land there instead.
A dead letter queue is a durable holding area for webhook events that failed processing and exhausted their retry budget, instead of being silently dropped. It is a pattern, not special software: after bounded retries — or a clearly non-transient failure like a signature that will never verify — move the event somewhere inspectable, alert on it, and reconcile against delivery data.
Retries buy time, not certainty. Sooner or later a webhook delivery fails in a way that no amount of resending fixes — a malformed payload your handler can't parse, a signature that won't verify, an endpoint that's been down for hours. When that happens, the event has exactly two possible fates: it gets silently dropped, or it lands somewhere you can find it and deal with it later. A dead letter queue is what makes the second outcome the default instead of the first.
PostGate handles OAuth, publishing and delivery evidence for every network through one API. Start free or read the docs.
What is a dead letter queue, actually?
A dead letter queue (DLQ) is a holding area — a table, a queue, a bucket, whatever your infrastructure already uses — for messages that failed processing and exhausted their retry budget, instead of being thrown away when the last retry fails. It's not a special piece of software; it's a pattern: when something can't be processed after reasonable effort, move it somewhere durable and inspectable rather than letting the failure vanish into a log line nobody reads until a customer complains that a post never updated.
What does PostGate document about its own retry behavior?
PostGate retries a webhook delivery when your endpoint times out or returns a non-2xx status — see Webhooks. PostGate's own documentation doesn't publish an exact retry count or backoff schedule for webhook delivery specifically, so don't assume a fixed number of attempts or a guaranteed retry window; build your consumer to be resilient regardless of how many times a given event is retried, and don't rely on retries alone as your recovery mechanism. Two things narrow that gap: use POST /api/webhooks/{id}/test to confirm your endpoint is reachable and verifying correctly after every deploy, and treat GET /api/posts/{id} — Delivery lifecycle — as your source of truth to reconcile against if you suspect an event was missed, since polling that endpoint doesn't depend on webhook delivery succeeding at all.
When should your consumer give up and dead-letter?
This applies on your side of the wire too, not just PostGate's: if your consumer enqueues events internally for out-of-band processing, that internal processing needs its own bounded retry policy — see Exponential backoff for social API retries for the schedule shape. Once an event has failed processing after a reasonable number of internal attempts, or fails in a way that's clearly not transient (a signature that will never verify, a data shape your code doesn't recognize), stop retrying it in place and move it to a DLQ instead. Continuing to retry something that can't succeed just delays the point where a human finds out about it.
async function processWebhookEvent(event, attempt = 0) {
const MAX_ATTEMPTS = 5;
try {
await handleEvent(event);
} catch (err) {
if (!isRetryableProcessingError(err) || attempt >= MAX_ATTEMPTS) {
await deadLetterQueue.insert({
event_id: event.id,
event_type: event.type,
payload: event,
failure_reason: String(err),
failed_attempts: attempt + 1,
moved_at: new Date().toISOString(),
});
return; // stop — a human or an alert should pick this up
}
throw err; // let your job runner's own retry/backoff handle this attempt
}
}What should you do with a dead-lettered event?
An event that lands in a DLQ and is never looked at again is barely better than one that was silently dropped — it just fails more slowly. At minimum, alert when the DLQ receives a new item so someone notices the day it happens rather than the week a customer asks why their analytics never updated. Beyond that, most DLQ entries fall into one of three actions: fix a bug and reprocess the stored payload, confirm the event is genuinely unrecoverable and archive it, or — for anything involving delivery state — reconcile against Delivery lifecycle data directly instead of trusting the stale webhook payload at all.
Why does this matter more for a publishing pipeline?
A dropped comment.created event is a missed notification. A dropped profile.reauth_required event is a connected account that quietly breaks while your product keeps reporting it as healthy, until a scheduled post fails and a customer asks why — see Why do my social accounts keep disconnecting?. A DLQ, paired with reconciliation against PostGate's own delivery and profile-health data rather than webhook payloads alone, is what keeps that gap from turning into a support ticket instead of an automated recovery.
Frequently asked questions
When should I move a webhook event to a dead letter queue?
When an event has failed processing after a reasonable number of bounded internal attempts, or when it fails in a way that is clearly not transient — a signature that will never verify, or a data shape your code does not recognize. At that point, stop retrying it in place and move it to the DLQ: continuing to retry something that cannot succeed just delays the moment a human finds out about it.
How many times does PostGate retry a failed webhook delivery?
PostGate retries a delivery when your endpoint times out or returns a non-2xx status, but its documentation does not publish an exact retry count or backoff schedule for webhook delivery — so do not assume a fixed number of attempts or a guaranteed retry window. Build your consumer to be resilient regardless, use POST /api/webhooks/{id}/test to verify your endpoint after every deploy, and treat GET /api/posts/{id} as the source of truth to reconcile against if you suspect an event was missed.
What should I do with events sitting in a dead letter queue?
First, alert when the DLQ receives a new item so someone notices the day it happens. Then most entries fall into one of three actions: fix a bug and reprocess the stored payload, confirm the event is genuinely unrecoverable and archive it, or — for anything involving delivery state — reconcile against the delivery lifecycle data directly instead of trusting the stale webhook payload. An event that lands in a DLQ and is never looked at again just fails more slowly.
Why do dropped webhooks matter so much for social publishing?
Because the cost is asymmetric. A dropped comment.created event is a missed notification, but a dropped profile.reauth_required event is a connected account that quietly breaks while your product keeps reporting it as healthy — until a scheduled post fails and a customer asks why. A DLQ paired with reconciliation against delivery and profile-health data turns that gap into automated recovery instead of a support ticket.
