Aggregator order status
API
Aggregator order status
Inbound endpoint your delivery aggregator (Rappi / Uber / Didi / iFood …) POSTs to as it moves an order through its delivery lifecycle. Fire mirrors the status onto the order and logs the event. Status is passthrough — the aggregator’s own labels, stored verbatim.
POST
Aggregator order status
This endpoint is inbound — your delivery aggregator (Rappi, Uber, Didi, iFood, PedidosYa, Glovo…) POSTs to it whenever it advances the order on its side: a courier was assigned, the order was picked up, it is on route, it was delivered, and so on. Fire authenticates the request, resolves the order, mirrors the latest status onto
Both are vendor-scoped: the resolved order must belong to the account + vendor bound to your API key, and its channel must match
A
The raw status is stored as-is; any friendly, translated label is resolved at display time from the channel’s status catalog — the stored value never changes.
orders.aggregator, and records the event in its aggregator event log for observability.
This endpoint is asynchronous. Fire authenticates, resolves the order (tenant + channel guards), deduplicates, and enqueues the event — then returns
202 Accepted with a webhookEventId (typically under 100 ms). The order mirror is updated 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, order not found, wrong tenant or channel, conflicting ids) are rejected synchronously with 4xx before the 202.Status is passthrough. Aggregators don’t share a status vocabulary, so Fire does not impose an enum:
status is stored verbatim as the event type (courier_assigned, on_route, entregue, whatever your channel uses). The order’s current status is the one with the latest occurredAt — not a fixed lifecycle order. There is no anti-regression gate: a later timestamp wins, period. Friendly, translated labels are a display concern resolved from the channel catalog, never enforced here.No Fire-emitted
eventId to echo. Unlike the fiscal callback and KDS statuses — which echo an event.id Fire emitted — an aggregator status is a spontaneous external event. Fire is not the source of truth here, so there is no source-of-truth check on an eventId. Instead you tell Fire which order the status belongs to, via order resolution below. Idempotency is keyed on your providerEventId (see Idempotency).Order resolution
You must tell Fire which order this status belongs to. There are two paths, and you may send either or both — at least one is required:| Field | Resolves by | When to use |
|---|---|---|
orderId | Our orders.id (UUID) | You stored Fire’s order id (e.g. echoed from an outbound event or the injection response) |
externalOrderId | The external (XMART/aggregator) order id, matched against the order’s metadata.order_id | You only know your own order reference |
channelCode.
Authentication
This endpoint requires a vendor-scoped API key with thewebhooks:aggregator scope (account + vendor binding). Fire enforces that the resolved order belongs to that vendor. Keys without the scope, or without a vendor binding, are rejected with 403 Forbidden.
Your Fire vendor-scoped API key with scope
webhooks:aggregator. 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 aggregator/channel code — it must equal the order’s
metadata.channel.code (channels.code, e.g. RAPPI, UBER, or the numeric channel id like 99). If it doesn’t match the resolved order’s channel, Fire responds 403.The raw delivery status, passthrough — stored verbatim as the event type. Any non-empty string is accepted (
accepted, courier_assigned, picked_up, on_route, delivered, cancelled, or your channel’s own labels). No enum is enforced.Your aggregator’s own id for this delivery — the idempotency key (together with
channelCode). Fire maps (channelCode, providerEventId) to a stable internal event id, so resending the same pair with the same status is a safe replay. Use your native event id if available; otherwise a UUID.ISO 8601 UTC timestamp of when the status changed on the aggregator side — not when it was sent. This is what orders the journey: the status with the latest
occurredAt is the order’s current status.UUID of the order in Fire. Required if
externalOrderId is absent. See Order resolution.The external (XMART/aggregator) order id, matched against the order’s
metadata.order_id. Required if orderId is absent. See Order resolution.Optional free-form bag of extra fields (courier name, tracking url, etc.). Stored as-is, not validated.
Examples
The reports of the same delivery share achannelCode and resolve to the same order; each carries its own status, providerEventId, and occurredAt:
courier_assigned (by Fire orderId)
on_route (by external order id)
delivered (both ids — must point to the same order)
Response
On success the endpoint returns202 Accepted — the event was authenticated, resolved, deduplicated, and enqueued. A 202 does not mean the order mirror was updated yet; that happens asynchronously. Use the status endpoint to confirm.
The body carries two distinct ids: eventId is Fire’s stable internal id for this (channelCode, providerEventId) pair, 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 status report was already ingested (same order, same (channelCode, providerEventId), same status) — the existing record is returned and nothing is re-enqueued. false for a fresh report (including a different status of the same delivery — that’s a new step, not a duplicate).Fire’s stable internal id derived from
(channelCode, providerEventId).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 the order mirror was updated, 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).Processing attempts so far.
On success, the worker outcome — e.g.
{ "kind": "merged", "current": "delivered" }.{ "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:aggregator key you used for the event.
Idempotency & the journey
Fire deduplicates on the order plus(channelCode, providerEventId) and the status — not on a Fire-emitted eventId. This is what makes the journey work while keeping retries clean:
| Scenario | Result |
|---|---|
courier_assigned, then on_route, then delivered — different status (and providerEventId), same order | each is a new step → 202 duplicate:false |
The same report resent — same order, same (channelCode, providerEventId), same status | 202 duplicate:true — existing record returned, not re-processed |
Neither orderId nor externalOrderId sent | 400 |
| Order not found for this vendor | 404 |
orderId and externalOrderId resolve to different orders | 409 |
channelCode ≠ the order’s channel, or key not for this vendor | 403 |
| Auth store momentarily unreachable | 503 — transient, retry |
A different
status is never a duplicate. Because aggregators legitimately report many statuses for one delivery, two reports with the same (channelCode, providerEventId) but a different status are two distinct steps — Fire stores both. Keep providerEventId unique per status report to avoid accidentally replaying a step.The order mirror
Once processed, the latest status is mirrored onto the order’saggregator block, with the full journey kept in history (ordered by occurredAt). The current status is the entry with the latest occurredAt:
orders.aggregator
Related
Inject order
The order injection endpoint that creates the order this status references.
KDS order status
The sibling inbound webhook for kitchen state — same async + queue model, but Fire-emitted
eventId and anti-regression.Fiscal callback
The fiscal inbound webhook — same async + idempotency envelope.
Authentication
How API keys, scopes, and vendor binding work.

