Do I need a queue for webhooks?
You need somewhere durable to store each event before you answer the sender, and something that processes it afterwards — and that is a queue whether or not you call it one. If your handler finishes quickly and touches only your own database, store the event and process it in the same request. Once it calls a slow or unreliable service, store the event, answer 200, and process it from the store, so a slow service never becomes a sender timeout followed by a duplicate delivery.
A table as the queue
// A table you already have is enough of a queue: store first, answer, process after.
// store: { insertIfAbsent(id, event), pending(limit), markDone(id), markFailed(id, error) }
export async function receive (event, store) {
await store.insertIfAbsent(event.id, event) // if this throws, answer 500 and the sender retries
return 200
}
export async function drain (store, process, limit = 100) {
const result = { done: 0, failed: 0 }
for (const row of await store.pending(limit)) {
try {
await process(row.event)
await store.markDone(row.id)
result.done++
} catch (err) {
await store.markFailed(row.id, String(err))
result.failed++
}
}
return result
}Why answering late causes duplicates
A sender waits a limited time for your answer. If your handler is still working when that time runs out, the sender records a failure and, if it retries, sends the event again — while your first attempt may still finish. Storing first and answering at once keeps the answer fast, and keying the store on the event id makes the second copy harmless.
When you need more than a table
A table stops being enough when more than one worker drains it at the same time, and each has to claim a row without taking another worker's. At that point claim rows with a lease — see How do I resume a webhook job after a crash?.
Not writing the queue at all
A gateway that stores every event and retries each delivery is this queue, already built: Slashbin stores each payload it receives and retries each destination on its own backoff until the delivery succeeds or its attempts run out.
Need somewhere for the next project's webhooks to land? Slashbin's Free plan takes an account, not a card.
Start Free
(opens in a new tab)