The POS says it sent the event and your system never moved
In short
A POS status event dies in 1 of 5 places: no subscription for that location, no such event on that plan, a slow acknowledgement under load, verification failing after a key rotation, or a platform that never emits for orders it did not originate. In a restaurant the event expires inside one cook, so the durable fix is a sweep over open orders, not a better retry policy.
Key takeaways
- A restaurant event has a short shelf life: a status change that lands after the food does has no value left.
- Only 4 events really matter mid-service — accepted, fired, item unavailable, check closed. Subscribe narrowly and sweep for the rest.
- Acknowledge in milliseconds and process in a queue. A handler that does work before returning 200 fails under exactly the load you built it for.
- During a signing key rotation, verify against both keys. A silent verification failure looks identical to no delivery at all.
- Poll open orders every 30 to 60 seconds. Missed events then become invisible rather than becoming Monday's support ticket.
The guest is looking at a screen that says accepted. The food went out 8 minutes ago. Somewhere between the terminal and your handler an event about that check stopped moving, and every minute the system spends not knowing is a minute the guest spends being told something false.
Restaurant events are unusual in that their value expires. A payment event that arrives an hour late still reconciles correctly; an order-fired event that arrives an hour late describes a meal that has been eaten. Roughly 12 minutes of cook time is the whole window in which the information is worth anything, which is why this page ends in a polling sweep rather than in retry semantics.
The four events a restaurant integration actually needs
Subscribe narrowly. A firehose of every catalogue and employee event buries the 4 that carry service consequences, and each of those 4 drives a different decision on your side.
- Order accepted. The restaurant has taken the order. This is what moves a guest from "sent" to "confirmed", and its absence is the most visible failure of the 4.
- Order fired or in progress. The kitchen has started. This is what makes a ready-time estimate honest instead of decorative.
- Item unavailable or eighty-sixed. Something on the check cannot be made, and a guest who learns this at collection has had a worse experience than one who learns it in 90 seconds.
- Check closed or voided. The transaction reached a terminal state, which is what lets your order stop being open and start being reconciled — against the object described in the check: what an online order turns into.
The event ledger, compared against one service period
Do not start in the platform's delivery dashboard. Start with your own record, because the question is not what the platform sent, it is which of your orders lack an event they should have.
- Record every inbound event, before verification and before processing: received timestamp, event type, check id, delivery id, signature verification result, and the HTTP status you returned. Keep the raw body.
- Pick one service — a Friday dinner, 18:00 to 22:00 — and list every order your system created in that window with the events it received and their timings.
- Pull the POS order history for the same window and the same location, and join on check id.
- Classify each gap. No event received at all, event received and rejected at verification, or event received and processed but the state never changed on your side. These are 3 different bugs and only the first is a delivery problem.
- Note the shape of the gap. All events missing for 1 location points at subscription; 1 event type missing everywhere points at the plan; a scatter concentrated in the busiest 20 minutes points at your own handler.
No subscription exists for that location
The dullest cause and the most common. Event subscriptions are frequently created per merchant location, in the same act that grants API access, and a location added later has API credentials with no subscription behind them. Everything else works — you can read the order, you can write a check — and no event ever arrives.
Ask the platform which subscriptions exist for your application, per location, rather than consulting your own configuration. This is the same per-location asymmetry that produces an integration working at one store and failing at the rest, and it should live in the same inventory.
The event type does not exist on the plan that restaurant is on
Platform capability varies by subscription tier and by module. A restaurant on a basic package may not emit kitchen-level events at all, because it does not have the kitchen module those events come from. Your integration is not broken; the events were never available to this merchant.
Establish this before build, per platform and per tier, and record it as a capability matrix rather than an assumption. Where an event genuinely does not exist, polling is not a workaround, it is the interface — and if the platform exposes neither, that is one of the situations weighed in when the POS has no usable API.
Your endpoint returned 200 slowly, and the platform stopped waiting
This is the cause that hides best, because it only appears at load. Delivery timeouts on webhook endpoints are short — a handful of seconds is typical, and each platform documents its own figure, which is worth reading rather than guessing. A handler that writes to your database, calls the POS again to fetch the full check, notifies the guest and then returns 200 will comfortably exceed that during a rush, and the platform records a failed delivery for an event you actually processed.
| Cause | Distinguishing signal | Fix |
|---|---|---|
| No subscription for that location | Every event type missing, at 1 site only | Create the subscription; add a subscription column to the credential inventory |
| Event type not offered on that plan | 1 event type missing at every site on that tier | Poll for that state; record the capability per tier |
| Slow acknowledgement under load | Gaps cluster in the busiest minutes; platform shows delivery failures | Acknowledge immediately, process from a queue |
| Signature verification failing | Events received and rejected, starting at one timestamp | Verify against old and new signing keys through the rotation |
| Platform does not emit for third-party orders | Events arrive for terminal orders, never for injected ones | Poll your own orders; confirm the documented emission rules |
The repair is the standard one and it is worth stating plainly: read the body, verify the signature, write it to a durable queue, return 200. Everything else happens after the response. That is the difference between an integration that degrades under a rush and one that fails during it.
Signature verification failing quietly after a key rotation
Signing keys rotate, sometimes on a schedule and sometimes when a merchant reconnects the integration. A handler that verifies against a single stored key starts rejecting every delivery from the moment the rotation takes effect, and because it rejects them cleanly, your error rate looks like a delivery outage rather than a configuration change.
Hold 2 valid keys through any rotation window and accept a signature matching either. Log verification failures with the delivery identifier and the key you tried, and alert on the first one rather than on a rate — the first rejection and the thousandth mean the same thing here.
The platform never emits for orders it did not originate
Some platforms only raise lifecycle events for activity originating on their own terminals, or route events about a check to whichever partner created it. If every event you receive concerns walk-in orders and none concerns the orders you injected, this is the answer, and no amount of subscription work will change it.
This is one of the genuine differences between running your own integration and going through a marketplace connector, where the platform is already the originator — the trade set out in a marketplace connector or an app of your own. Read the platform's documented emission rules before designing around events at all.
The sweep that turns a missed event into a non-event
Events are an optimisation. The system of record is the POS, and you can ask it. A sweep that polls the current state of open orders makes every cause above survivable, including the ones you cannot fix.
- Scope it to open orders only — anything not in a terminal state, created within the last 4 hours. That set is small even at a busy site, usually tens of checks rather than thousands.
- Poll every 30 to 60 seconds during trading hours and back off to 5 minutes outside them. Match the cadence to the cook, not to the calendar.
- Use a list-changed-since call where the platform offers one, so the sweep costs a handful of requests per location per minute rather than one per order.
- Apply the same state transition logic as the event handler, keyed on the check id and its version, so an event and a sweep arriving in either order produce the same result.
- Alert when the sweep is the thing that moved an order more than a small share of the time. That ratio is your real webhook health metric, and it degrades before anyone complains.
Treat events as an optimisation over polling, not as a source of truth. Then a missed event costs you 40 seconds instead of a service.
General webhook engineering — signature schemes, delivery guarantees, idempotency keys, dead-letter handling — is a broad topic and this page has deliberately stayed inside the restaurant version of it. What is specific here is the clock: a 4-hour window, a 60-second cadence, 4 event types. Any automation hanging off a status transition inherits the same fragility, including enforcing a no-show charge, which fires on an event that may never arrive.
Two related investigations, if the events are arriving and the numbers still look wrong: your total and the POS total never quite agree covers reconciliation of amounts, and your test orders are now in the owner's sales report covers the environment mistake that puts synthetic checks in a real day. Building the ledger, the queue and the sweep is ordinary product build work. The rest of this silo sits under integrating with the POS on the counter, inside our restaurants and food service practice.
Frequently asked questions
Short answers to the follow-ups this page tends to raise.
Why did a POS webhook never reach our system during service?
There are 5 usual answers, and your own inbound event log separates them in minutes. No subscription exists for that location; the event type is not available on the restaurant's plan; your endpoint acknowledged too slowly under load; signature verification started failing after a key rotation; or the platform does not emit that event for orders it did not originate. Only the third is entirely within your control.
Should we poll the POS as well as listening for events?
Yes, and treat the polling as primary. Scope a sweep to orders that are not yet in a terminal state and created in the last few hours, run it every 30 to 60 seconds during trading, and apply the same state logic as your event handler. Events then become a latency improvement rather than a dependency, which matters because a restaurant status change is worthless once the food has gone out.
How fast does a webhook endpoint have to respond?
Fast enough that the platform does not time out the delivery, which is typically a small number of seconds and is documented per platform. The safe design is to verify the signature, write the raw event to a durable queue and return 200 immediately, doing all real work afterwards. Handlers that call back to the POS before responding are the ones that fail in the middle of a rush.
How do we tell a missed delivery from a rejected one?
Log every inbound request before verification, including the delivery identifier, the signature check result and the status you returned. A missed delivery leaves no row at all; a rejected one leaves a row with a failed verification, usually starting sharply at a single timestamp, which points at a signing key rotation rather than a network problem.
- pos
- webhooks
- order status
- diagnostics
The work behind this page
Builds from our portfolio that this page draws on.
Read next
- It works at the first location and returns an auth error at the other fourIdentical code, one store working and four rejecting, means authorisation was granted per merchant location and only completed once. The fix is an inventory, not a retry.diagnostic
- External IDs: the mapping table nobody designs until it breaksThe identity map between your catalogue and the till is the contract that makes every order possible, and 4 routine events break it silently. Design it as a versioned artefact.definition
- Order source and dining option: two fields your reporting rests onTwo small POS fields decide whether an injected order is taxed, routed and attributed correctly — and a wrong value is invisible until the first report nobody can answer.definition
- The full menu sync hits the limit before it finishesA sync that works on a 40-item menu and dies on a 400-item one is not too big. It is making one request per item, at the same minute as every other location.diagnostic
- The order lands in the POS and nothing prints at the stationThe POS accepted your order and the line never saw it. Routing is configuration — dining option, revenue centre, station map — and no field in your payload can override it.diagnostic
- What a POS partner programme gates, and what it does notA partner programme is a commercial gate wearing technical clothing. It controls scopes, production credentials and listing — and the wait is somebody else's decision, not engineering.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