Orders that sit in pending forever: tracing the transition that never fired
In short
Group every pending order by age bucket and by the last event recorded against it, then read the grid. A tight cluster at one age with the same last event is a lost inbound event. A flat spread with 'created' as the last event is a notification that never landed. A pile older than a fixed date, all in the same state, is a state nobody ever built an exit from. Three shapes, three different repairs.
Key takeaways
- Age alone tells you nothing; age crossed with the last recorded event tells you almost everything.
- Any state that appears as a destination and never as an origin over 90 days is terminal in practice.
- A notification has three separate failure points — never enqueued, never delivered, never seen — and three different fixes.
- Providers retry inbound deliveries for a bounded window and then stop, so a rotated secret costs you a permanent gap.
- Every state a human must exit needs an owner, a deadline and a defined action when the deadline passes.
- Orders created on Friday evening measure whether your state machine depends on somebody being at a desk.
Three different failures present as a pending queue, and one query separates them before any triage begins. Group every order still in pending by age bucket — under 2 hours, 2 to 24 hours, 1 to 3 days, 3 to 14 days, older — and cross that with the last event type recorded against the order. The shape of the resulting grid names the population. A dense cluster in one age band all sharing a last event is an inbound event that stopped arriving at a moment in time. A flat spread across every band with 'created' as the last event is a human who was never told. A pile that is uniformly older than some date is a state with no outbound transition at all.
Payment failures are a separate population with their own diagnosis and are not covered here. Nor is the case where the order is fine and the availability underneath it was not — that symptom is traced in available in search and gone at checkout.
Split the queue before triaging any of it
This query needs a transitions table — a row per state change with its timestamp, its actor and its cause. If all you have is a status column, you can see that 340 orders are pending and nothing about why, and the investigation stalls immediately. That is the practical reason the argument in an event log or a status column for marketplace orders resolves the way it does: the projection is for reading, the log is for answering questions like this one.
| Shape in the grid | Population | The next check |
|---|---|---|
| Dense band at one age, same last event across all of it | An inbound event stopped arriving | Compare the sender's delivery log for that window against rows in your inbound event table |
| Flat across every age band, last event is 'created' | Nobody was ever told there was something to do | For each order, does a notification row exist, does it have a provider id, does it have a delivery receipt |
| Everything older than one date, all in the same state | A state with no outbound transition | List every state pair observed in 90 days and find states that are only ever destinations |
| A cluster whose ages match one deploy window | A regression shipped and was not noticed | Diff the transition code between the two releases either side of that window |
| Long tail concentrated on evenings and weekends | The state machine depends on staffed hours | Split age-in-state by hour of creation and day of week |
Four reasons a transition never fires
- The inbound event was dropped or never verified. A payment provider, a logistics partner or a seller's system was going to tell you something and the message did not land. The usual cause is dull: a rotated signing secret, an endpoint that started returning 500s during a deploy, or a signature check that fails silently and logs at debug level. Most providers retry on a backoff for a bounded window and then give up — read your provider's own documentation for its policy rather than assuming — so an outage longer than that window leaves a permanent hole that only a replay fills.
- The notification was never sent, never delivered, or never seen. Three failures wearing one face. Never sent: no notification row exists, because the enqueue happened inside a transaction that rolled back, or behind a condition nobody tested. Never delivered: a row exists with no provider identifier, or with a bounce. Never seen: delivered to a push token that expired months ago, an address that forwards to a shared inbox, or a seller who has notifications muted. Each has a different fix and they are indistinguishable until you look at all three columns.
- The state is terminal in practice. Somebody defined 'awaiting seller response' and built the way in without building the way out. The confirming query is straightforward: list every (from state, to state) pair observed in the last 90 days, then find any state that appears as a destination and never as an origin. Those states are where orders go to stop. This is also the point at which the difference between a true state and a flag becomes expensive, which is the distinction drawn in order states versus order flags.
- The timeout was specified and never built. The requirements said sellers have 24 hours to accept. Somebody built the acceptance path, the reminder email and the countdown in the UI, and nobody built the transition that fires when the countdown reaches zero. The UI shows expired; the record says pending. Search the codebase for the timeout constant and see whether anything other than a template reads it.
The queue has to drain when nobody is at a desk
Split age-in-state by the hour and weekday an order entered the state. If the p95 for orders created after 18:00 on a Friday is 60 hours while the Tuesday-morning figure is 90 minutes, the state machine is not broken — it is staffed, and you have discovered that its throughput is a rota rather than a system. Reminders will not fix that. The two things that do are an automatic default at the deadline, and routing that does not depend on one person.
- Give every human-exited state a defined default. Auto-accept, auto-decline, auto-reassign or auto-refund — the business chooses, but something must happen without a person. A deadline with no default action is a reminder, and reminders decay into noise within about a month.
- Escalate to a role that is always occupied, not to an individual. The most common weekend failure is a queue routed to a named seller who is on holiday, with no second recipient anywhere in the configuration.
- Make the default visible before it fires. A seller who loses an order to auto-decline while asleep will forgive a system that warned them and will not forgive one that did not.
- Check whether the orders piling up were routed to the right seller in the first place. Unaccepted orders concentrated on a handful of sellers is an allocation problem rather than a notification problem, which is the territory of hand-tuned weights versus learned ranking for a small market.
- Treat the sweeper that applies defaults as a piece of production software with alerting, not as a cron line in a comment. Ours is the kind of unglamorous machinery we build as AI agents and automation when the decision at the deadline needs more than a fixed rule.
A state nobody can leave is not a state. It is a place the platform sends orders it has no plan for.
Replay, notify, or build the missing state
- Does the last recorded event on a stuck order come from outside your system? If it does and no successor arrived, count inbound deliveries per day for the affected window against the sender's own log. A gap confirms lost events, and the repair is a replay from the sender plus verification and retry on the receiving endpoint.
- If the last event is internal and a person was supposed to act, check for the notification row, then its provider identifier, then its delivery receipt. Missing row means the enqueue never happened. Row without identifier means the send failed. Delivered with no engagement across many orders means the channel is dead and needs replacing rather than retrying.
- If a person did act and the order still did not move, the transition itself is the fault: a guard clause that never passes, a permission check that returns false for the actor, or an update that writes a column nothing reads.
- If no transition exists at all out of that state, stop treating it as an incident. Design the exit: what ends this state, who may end it, what happens by default, and after how long.
- Once the population is drained, add the standing check that would have caught it — a count of orders in each state older than that state's expected dwell time, evaluated hourly, alerting when it grows rather than when it is non-zero.
The permanent fix: states designed with their exits
The permanent fix is not a bigger dashboard. It is that every state in the machine carries a declared expected dwell time, an owner and a default. States that cannot answer those three questions are the ones that fill up, and they fill up quietly. That property — states designed with their exits rather than only their entrances — is what separates a marketplace whose ops team lives in a queue from one whose ops team lives in exceptions, and it is part of the wider argument about building software that is honest about its own failure modes set out in what an AI-native product studio actually is.
If the pending order is one of a pair fighting over the same supply, the diagnosis is different and starts elsewhere — see two confirmed bookings on one slot. The rest of this silo sits under marketplace architecture and the transaction data model, and the wider practice is described under marketplaces and two-sided platforms.
Frequently asked questions
Short answers to the follow-ups this page tends to raise.
How do I tell a missed webhook from a seller who simply has not responded?
Look at what the last recorded event is and where it came from. If the last event is an external one and its expected successor never arrived, compare your inbound event count for that window against the sender's delivery log — a gap proves the message never landed. If the last event is internal and a notification row exists with a delivery receipt, the message arrived and the person did not act, which is a routing or workload problem rather than an integration one.
What is the fastest way to find a state that has no way out?
List every (from state, to state) pair observed in your transitions table over the last 90 days, then look for states that appear as a destination and never as an origin. Those are terminal in practice regardless of what the design document says. Run the same query per order type, because a state can have exits for one transaction shape and none for another that was added later.
Should stuck orders be auto-cancelled after a fixed time?
Only where cancellation is the right default for that state, and the default is a business decision rather than an engineering one. What matters is that every state a human must exit has some defined action at its deadline — accept, decline, reassign, refund or escalate. A deadline with no action attached produces reminders, and reminders stop being read. Auto-cancel is one legitimate answer among several, not the general one.
Is a dashboard of pending orders enough?
No, because a queue that is always non-empty stops being read within weeks. Alert on the derivative instead: the count of orders exceeding each state's declared expected dwell time, and whether that count is growing. A queue with 40 items that has had 40 items all month is healthy; one that had 5 yesterday and 40 today is an incident, and only the second should wake anyone.
- order states
- webhooks
- queues
- incident response
The work behind this page
Builds from our portfolio that this page draws on.
DockQueue
A dock scheduling and yard management platform that lets carriers self-book dock appointments, assigns the right door on arrival, and tracks every trailer's dwell and detention live.
LogisticsHaulBoard
An AI freight load board that matches every open load to the best-fit carrier, prices each lane on live spot-rate data, and tracks broker margin on every move.
LogisticsRead next
- Order states versus order flags: the distinction that keeps a transaction consistentOne question admits a value to the state machine: does it change which transitions are legal from here? Everything that fails that test is a flag on the record.definition
- Available in search, gone at checkout: locating the stale layerRe-ask the source of truth at the moment of failure. If it agrees with the index, your two paths apply different rules; if it disagrees, something is holding a stale copy.diagnostic
- Availability: a set of intervals, not a grid of day cellsAvailability is not a stored fact. It is the answer to a question, computed from recurring rules, exceptions and what has already been consumed — and a day-cell table is a cache of that answer.definition
- Inventory hold: the row that exists so the second buyer is refusedA hold is a record, not a screen state. It names one unit of supply, one claimant, one expiry and one reason — and while it lives, nothing else may overlap that unit.definition
- Listing snapshot: the version of the offer the buyer agreed toAn order that reads its terms from a live listing is an order whose terms change when the seller edits. The snapshot is the copy that makes it defensible.definition
- The unit of supply: the thing your marketplace is actually allocatingNot what you list, and not what you invoice: the unit of supply is the smallest thing two buyers can want at once when only one of them can have it.definition
Working on something in this space?
Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.
Start the conversation