Ordering
Ordering makes a receiver see an endpoint’s events in the order they were created. It is off by default, works per endpoint, and applies to outgoing deliveries only.
Turn it on
Section titled “Turn it on”Set orderingEnabled on the subscription:
curl -X POST "$RAILHOOK_URL/api/v1/projects/$PROJECT_ID/subscriptions" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"endpointId":"'"$ENDPOINT_ID"'","eventType":"order.*","orderingEnabled":true}'With ordering off, deliveries to the same endpoint can overtake each other, which is where most of the throughput comes from.
How it works
Section titled “How it works”| Term | Meaning |
|---|---|
| Sequence number | A position stamped on each delivery to an endpoint, saying which of two deliveries came first regardless of when a worker picks them up. Sent as X-Sequence-Number; 0 when ordering is off. |
| Ordering buffer | Where a delivery waits while the deliveries ahead of it have not finished. Waiting is a deferral: it does not use an attempt. |
| Gap | The range between the last position the endpoint received and a waiting delivery, containing at least one unresolved delivery. The whole range is checked, not only the previous position. |
A delivery goes out only when everything ahead of it for that endpoint has succeeded or been given up on. A delivery moved to Failed Messages stops blocking the ones behind it.
When a gap never closes
Section titled “When a gap never closes”Without a limit, one delivery that never resolves would block the endpoint for good. So once a waiting delivery has waited out the gap timeout and nothing in the gap is still moving, it is let through out of order, and the event is counted.
Both conditions matter. A delivery between two retries is a gap about to close, so the deliveries behind it keep waiting however long they have waited. Otherwise the first ordinary retry would break the order, since the first retry delay and the gap timeout are both one minute. The timeout only releases a gap with nothing in flight and nothing due.
| Setting | Default |
|---|---|
ORDERING_GAP_TIMEOUT_SECONDS |
60 |
| Metric | webhook_ordering_gap_timeout_total |
What it costs
Section titled “What it costs”| Cost | Why |
|---|---|
| Throughput | An ordered endpoint is effectively serial. Its response time is your ceiling. |
| Head-of-line blocking | One slow or failing delivery holds up everything behind it until it resolves or the gap times out. |
| Replays go to the back | A replay creates new deliveries with new sequence numbers, so a replayed event arrives after what is already waiting, not in its original place. |
What is and is not promised
Section titled “What is and is not promised”- Order is per endpoint, never global. Two endpoints subscribed to the same event receive it independently.
- Delivery is still at-least-once. Deduplicate on
X-Delivery-IdorIdempotency-Key. - The order is best-effort under the gap timeout. If you need an order that never yields, put your own sequence field in the payload and check it in the receiver.
- Incoming webhooks have no ordering: Railhook cannot know the order the provider intended. Use the provider’s own sequence field if it has one.