FIFO Ordering
By default Hookbase delivers an endpoint's messages concurrently, and a failed message is retried on its own schedule while newer ones carry on. That gives the best throughput, but a receiver can see order.updated before order.created, or an older update after a newer one.
Turn on FIFO ordering for an endpoint and Hookbase delivers its messages one at a time, in the order they were sent. A message is not sent until the one before it has finished. FIFO ordering requires the Pro plan or above.
Use it when the receiver applies events as a sequence: a state machine, a ledger, a sync feed, or anything that would otherwise need to reorder or deduplicate on its side. Leave it off when messages are independent, since ordered delivery is slower (see Throughput).
Turn it on
In the dashboard, open an endpoint's Edit dialog (or Create Endpoint) and switch on FIFO ordering. Through the API, set fifoEnabled:
curl -X PATCH https://api.hookbase.app/api/organizations/$ORG_ID/webhook-endpoints/$ENDPOINT_ID \
-H "Authorization: Bearer $HOOKBASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "fifoEnabled": true }'FIFO works the same way for every push endpoint kind: HTTP webhooks, SQS, SNS, EventBridge, Pub/Sub and S3. It does not apply to polling endpoints, whose consumer already pulls messages in order, and the API rejects it there with 400.
What "in order" means
Ordering is per endpoint. Each message gets its place when Hookbase accepts the send (POST /v1/events returns), so the order is the order your sends were accepted.
- Sends you make one after another, waiting for each response, arrive in that order.
- Sends you make in parallel have no defined order relative to each other. If two events must arrive in a particular order, send the second after the first has been accepted.
- Different endpoints are independent. A slow or failing endpoint never holds up another one, even for the same event.
Head-of-line blocking
The oldest unfinished message is the head of the endpoint's queue. Only the head is ever being delivered.
- Hookbase delivers the head.
- If it succeeds, the next message becomes the head and is delivered straight away.
- If it fails, it is retried on the endpoint's normal retry schedule. Every message behind it waits, in order, for as long as the head is being retried.
- If the head uses up its attempts, it moves to the DLQ like any other failed message, and the next message proceeds.
So one failing message delays everything behind it by up to its whole retry schedule (about 13 minutes with the default 5 attempts), and then the queue moves on without it. A dead-lettered message does not stop the queue for good: ordering is kept among the messages that are delivered, and the DLQ tells you which ones were not.
The endpoint page shows the queue: its depth, how far behind it is, and whether the head is blocked and why. The same is available from the API:
curl https://api.hookbase.app/api/organizations/$ORG_ID/webhook-endpoints/$ENDPOINT_ID/fifo-state \
-H "Authorization: Bearer $HOOKBASE_API_KEY"{
"data": {
"fifoEnabled": true,
"state": "blocked",
"blocked": true,
"blockedReason": "retrying",
"queueDepth": 42,
"waitingCount": 41,
"oldestQueuedAt": "2026-10-18T10:00:00.000Z",
"lagSeconds": 312,
"head": {
"messageId": "…",
"eventType": "order.updated",
"status": "awaiting_retry",
"attempts": 2,
"maxAttempts": 5,
"nextAttemptAt": "2026-10-18T10:06:00.000Z",
"lastResponseStatus": 503,
"lastError": "HTTP 503"
},
"leaseSeconds": 300
}
}state | Meaning |
|---|---|
idle | Nothing queued. |
delivering | The head is being sent. |
blocked | The head failed and is waiting to be retried (blockedReason: "retrying"), or the circuit breaker is open ("circuit_open"). |
paused | The endpoint is disabled (blockedReason: "endpoint_disabled"). |
off | FIFO is not enabled. |
Messages waiting behind the head have the status Queued (FIFO) (fifo_waiting) in the message log.
Failures, outages and the circuit breaker
- Circuit breaker open. Without FIFO, new messages for an endpoint with an open circuit are skipped. With FIFO they are kept and queued, and the head waits for the cooldown without using up an attempt. When the circuit lets a probe through, the queue continues in order.
- Endpoint disabled. The queue is paused, not dropped. New sends are not queued while it is disabled (as for any disabled endpoint), and everything already queued is delivered, in order, once you enable it again.
- Interrupted delivery. If a delivery is interrupted (for example a worker restarts mid-request), the head is sent again after a 5 minute lease. Delivery is at least once, as it is without FIFO, so your receiver should still deduplicate by the
webhook-idheader. - Retention. A queued message that is still waiting when your plan's retention period runs out moves to the DLQ with the reason
fifo_expired.
Replays and DLQ retries
Replaying a message, replaying an endpoint's failed messages, or retrying from the DLQ sends a new copy outside the ordered queue. It is delivered straight away and does not wait behind (or hold up) the queue. That is usually what you want after an outage: the queue keeps moving and you choose what to resend. If your receiver relies on order, replay messages in the order you need them, one at a time.
Turning FIFO on or off
- On. Messages sent after you turn it on are ordered. Messages already in flight when you switch it on finish under the normal (unordered) pipeline, so allow them to drain if strict order from the first message matters.
- Off. Everything still queued is handed back to normal delivery and sent concurrently within about a minute. Nothing is dropped. Turning FIFO off is always allowed, whatever your plan.
- Plan downgrade. An endpoint that already has FIFO on keeps it after a downgrade, so your receiver is never switched to unordered delivery without warning. You can turn it off at any time, but turning it back on needs Pro or Business.
- Deleting the endpoint. Its queued messages are moved to the DLQ, as they would be for any deleted endpoint.
Throughput
An ordered endpoint has one request in flight at a time, so its throughput is about one message per round trip to your receiver: a receiver that answers in 100 ms gets roughly 10 messages a second. Retries and the endpoint's rate limit slow it further. If you need ordering but more throughput, split the stream across several endpoints (for example one per customer or per shard), since each endpoint is ordered independently.