Skip to main content
POST
/
api
/
v1
/
webhooks
/
kds
/
order-status
KDS order status
curl --request POST \
  --url https://app.fire.rest/api/v1/webhooks/kds/order-status \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <x-api-key>' \
  --data '
{
  "eventType": "<string>",
  "providerEventId": "<string>",
  "occurredAt": "<string>",
  "orderId": "<string>",
  "eventId": "<string>",
  "stationName": "<string>",
  "metadata": {}
}
'
{
  "received": true,
  "duplicate": false,
  "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "webhookEventId": "9e6c8af8-af80-4967-9422-c096ab43c0e7",
  "status": "queued",
  "firstReceivedAt": "2026-05-26T18:30:00.512Z",
  "message": "Event accepted and queued for processing."
}
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.
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 (preparingreadydispatched) 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:
eventTypeMeaningRank
order.preparingThe kitchen started working on the order1
order.readyThe order is prepared and ready for hand-off / pickup2
order.dispatchedThe order left the kitchen (delivered or picked up) — terminal3
Values are lowercase, dotted (order.preparing, not ORDER_PREPARING).

Authentication

This endpoint requires a vendor-scoped API key with the webhooks: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.
x-api-key
string
required
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.
Authorization
string
Optional Bearer <token> — accepted as a legacy alternative to x-api-key. Send one or the other.

Request body

eventType
string
required
The KDS lifecycle event. One of order.preparing, order.ready, or order.dispatched (lowercase, dotted).
providerEventId
string
required
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.
occurredAt
string
required
ISO 8601 UTC timestamp of when the event happened in the KDS — not when it was sent.
orderId
string
required
UUID of the order in Fire. Matches data.orderId in the order events. Fire correlates the event to this order; it must already exist.
eventId
string
required
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 eventId that does not reference a Fire event for that order is rejected with 400 before the 202.
  • One eventId for the whole journey. Send the same eventId for preparing, ready, and dispatched of that dispatch — they are told apart by eventType. (Each dispatch to a different device carries its own eventId, so two devices never collide.)
stationName
string
Optional originating KDS station (e.g. Hot line, Despacho 1). Stored for observability.
metadata
object
Optional free-form bag of extra fields. Stored as-is, not validated.

Examples

The three reports of the same dispatch share one eventId (the dispatch’s) and differ only by eventType and providerEventId:
order.preparing
{
  "eventType": "order.preparing",
  "providerEventId": "evt_2026-05-26_000122",
  "occurredAt": "2026-05-26T18:28:00.000Z",
  "orderId": "9f1c0e8a-1234-4abc-9def-0123456789ab",
  "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "stationName": "Hot line"
}
order.ready
{
  "eventType": "order.ready",
  "providerEventId": "evt_2026-05-26_000123",
  "occurredAt": "2026-05-26T18:30:00.000Z",
  "orderId": "9f1c0e8a-1234-4abc-9def-0123456789ab",
  "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "stationName": "Hot line"
}
order.dispatched
{
  "eventType": "order.dispatched",
  "providerEventId": "evt_2026-05-26_000124",
  "occurredAt": "2026-05-26T18:42:11.000Z",
  "orderId": "9f1c0e8a-1234-4abc-9def-0123456789ab",
  "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "stationName": "Hot line"
}

Response

On success the endpoint returns 202 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.
received
boolean
Always true when the request was accepted and enqueued.
duplicate
boolean
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).
eventId
string
Echo of the eventId you sent (the dispatch’s event.id).
webhookEventId
string
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.
status
string
Current queue status — queuedprocessingprocessed (and retry / failed / dead / ignored).
firstReceivedAt
string
ISO 8601 UTC timestamp of when Fire first received this report. Stable across retries.
message
string
Human-readable summary.
{
  "received": true,
  "duplicate": false,
  "eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "webhookEventId": "9e6c8af8-af80-4967-9422-c096ab43c0e7",
  "status": "queued",
  "firstReceivedAt": "2026-05-26T18:30:00.512Z",
  "message": "Event accepted and queued for processing."
}

Checking the outcome

Because processing is asynchronous, the 202 only confirms the event was queued. To see whether it was recorded, poll the companion status endpoint with the webhookEventId returned by the 202:
GET https://app.fire.rest/api/v1/webhooks/events/{webhookEventId}
x-api-key: <your webhooks:kds key>
status
string
Queue lifecycle: queuedprocessingprocessed (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).
attempts
number
Processing attempts so far.
result
object | null
On success, the worker outcome — e.g. { "kind": "recorded" } (advanced the order) or { "kind": "ignored" } (non-advancing).
error
object | null
{ "message": "…" } when the last attempt failed; null otherwise.
A 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:
ScenarioResult
preparing, then ready, then dispatchedsame eventId, different eventTypeeach is a new step202 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 orderId400
Order/event not under your API key’s account + vendor403
Auth store momentarily unreachable503 — 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 (preparingreadydispatched) — 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.ready after order.preparing) is recorded as the new state.
  • An event that does not advance — a regression (e.g. order.preparing arriving after order.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.
This makes the endpoint safe against out-of-order or late deliveries: send events in any order and Fire keeps the order at its most advanced stage.

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.