KDS order status
Partner APIs
KDS order status
Inbound endpoint your KDS POSTs to when a kitchen order changes state (preparing, ready, dispatched). Fire logs the event, deduplicates it, and enforces anti-regression so the order state never moves backwards.
POST
KDS order status
This endpoint is inbound — your Kitchen Display System (KDS) POSTs to it whenever an order advances in the kitchen: the kitchen started preparing it, it became ready for hand-off, or it was dispatched (delivered / picked up). Fire authenticates the request, correlates it to the order, applies idempotency and an anti-regression gate, and records the event in its KDS event log for observability.
Values are lowercase, dotted (
A
This endpoint is asynchronous. Fire authenticates, runs the source-of-truth + tenant guard, deduplicates, and enqueues the event — then returns
202 Accepted with a webhookEventId (typically under 100 ms). The event is recorded a moment later by a background worker (usually within ~2 seconds). To check the outcome, poll GET /v1/webhooks/events/{webhookEventId}. Client-fixable problems (bad payload, wrong eventId, wrong tenant) are rejected synchronously with 4xx before the 202.One dispatch event, several status reports. Unlike the fiscal callback — where each action carries its own
eventId — the KDS reports the whole journey (preparing → ready → dispatched) against one eventId: the id Fire emitted when it dispatched the order to your device. They are distinguished by eventType, not by eventId. See Idempotency & the journey.Event types
The KDS lifecycle has a strict order — an order is prepared before it is ready, and ready before it is dispatched:eventType | Meaning | Rank |
|---|---|---|
order.preparing | The kitchen started working on the order | 1 |
order.ready | The order is prepared and ready for hand-off / pickup | 2 |
order.dispatched | The order left the kitchen (delivered or picked up) — terminal | 3 |
order.preparing, not ORDER_PREPARING).
Authentication
This endpoint requires a vendor-scoped API key with thewebhooks:kds scope (account + vendor binding). Fire enforces that the order belongs to that account and vendor. Keys without the scope, or without a vendor binding, are rejected with 403 Forbidden.
Your Fire vendor-scoped API key with scope
webhooks:kds. Generate one from Developers → API Management for the account/vendor whose orders this key may report on.Optional
Bearer <token> — accepted as a legacy alternative to x-api-key. Send one or the other.Request body
The KDS lifecycle event. One of
order.preparing, order.ready, or order.dispatched (lowercase, dotted).Your KDS’s own id for this delivery. Stored for audit/forensics — it is not the idempotency key. Fire deduplicates on
(orderId, eventId, eventType), so you may send a fresh providerEventId on every retry. Use your KDS-native event id if available; otherwise a UUID.ISO 8601 UTC timestamp of when the event happened in the KDS — not when it was sent.
UUID of the order in Fire. Matches
data.orderId in the order events. Fire correlates the event to this order; it must already exist.Correlation UUID — the
event.id of the envelope Fire emitted when it dispatched the order to your device. Echo it exactly; never invent it.- Source-of-truth check. An
eventIdthat does not reference a Fire event for that order is rejected with400before the202. - One eventId for the whole journey. Send the same
eventIdforpreparing,ready, anddispatchedof that dispatch — they are told apart byeventType. (Each dispatch to a different device carries its owneventId, so two devices never collide.)
Optional originating KDS station (e.g.
Hot line, Despacho 1). Stored for observability.Optional free-form bag of extra fields. Stored as-is, not validated.
Examples
The three reports of the same dispatch share oneeventId (the dispatch’s) and differ only by eventType and providerEventId:
order.preparing
order.ready
order.dispatched
Response
On success the endpoint returns202 Accepted — the event was authenticated, validated, deduplicated, and enqueued. A 202 does not mean the event was recorded yet; that happens asynchronously. Use the status endpoint to confirm.
The body carries two distinct ids: eventId is the id you sent (echoed), webhookEventId is Fire’s id for the queued record. Same shape as the fiscal callback.
Always
true when the request was accepted and enqueued.true when this exact (orderId, eventId, eventType) was already ingested — the existing record is returned and nothing is re-enqueued. false for a fresh report (including a different eventType of the same dispatch — that’s a new step, not a duplicate).Echo of the
eventId you sent (the dispatch’s event.id).Fire’s id for the queued record. Pass it to
GET /v1/webhooks/events/{webhookEventId} to poll the outcome. On a duplicate it is the same id returned the first time.Current queue status —
queued → processing → processed (and retry / failed / dead / ignored).ISO 8601 UTC timestamp of when Fire first received this report. Stable across retries.
Human-readable summary.
Checking the outcome
Because processing is asynchronous, the202 only confirms the event was queued. To see whether it was recorded, poll the companion status endpoint with the webhookEventId returned by the 202:
Queue lifecycle:
queued → processing → processed (done) · failed / dead (gave up after retries) · retry (waiting for the next attempt) · ignored (handled, no action needed — e.g. a duplicate or non-advancing event).Processing attempts so far.
On success, the worker outcome — e.g.
{ "kind": "recorded" } (advanced the order) or { "kind": "ignored" } (non-advancing).{ "message": "…" } when the last attempt failed; null otherwise.404 is returned for unknown ids — or ids that belong to another tenant — with no existence leak. Authenticate with the same webhooks:kds key you used for the event.
Idempotency & the journey
Fire deduplicates on the triple(orderId, eventId, eventType) — not on providerEventId (which you may regenerate freely). This is what makes the journey work:
| Scenario | Result |
|---|---|
preparing, then ready, then dispatched — same eventId, different eventType | each is a new step → 202 duplicate:false. Same eventId is expected, not a conflict |
The same report resent — same (orderId, eventId, eventType) | 202 duplicate:true — existing record returned, not re-processed |
eventId not emitted by Fire for this orderId | 400 |
| Order/event not under your API key’s account + vendor | 403 |
| Auth store momentarily unreachable | 503 — transient, retry |
This is the key difference from the fiscal callback. There, one
eventId carries exactly one action, so reusing it for a different eventType is a conflict (409). Here, one dispatch eventId legitimately carries the whole journey (preparing → ready → dispatched) — the eventType is what tells the steps apart. Different devices get different dispatch eventIds, so their reports never collide.Anti-regression
The order state must never move backwards. Fire tracks the highest stage reached by the order (order.dispatched > order.ready > order.preparing) and compares each incoming event against it:
- An event that advances the order (e.g.
order.readyafterorder.preparing) is recorded as the new state. - An event that does not advance — a regression (e.g.
order.preparingarriving afterorder.ready) or a same-state repeat — is still recorded for observability, but flagged as non-advancing with a reason, and it does not move the order back.
Related
Inject order
The order injection endpoint that creates the order this event references.
Fiscal callback
The sibling inbound webhook — same async + idempotency + correlation model.
Authentication
How API keys, scopes, partner and vendor binding work.

