Skip to main content
You are reading the current (v1.1) contract for order.opened. v1.1 adds data.fiscalRepresentation: the fiscal numbering the point of sale obtained before injecting the order. It always travels: null when numbering was not attempted, and populated when it was — numberingStatus tells you how it ended. Carrying content does not mean the document is authorized. Additive only — nothing you already read changed.The block carries the order’s current fiscal document: the fields that mean the same in every country at the top, the authority identifiers inside countryData in that country’s own vocabulary, and the previous documents in history. When a cancellation numbers, the credit note moves to the top and the invoice moves down into history — with compensates pointing at it.
order.opened fires when an order is injected already open — it exists in Fire, the kitchen can start, but no payment has been confirmed. It carries the same V4 order snapshot as order.completed, so anything you can do on a completed order you can also do here. This is the event that makes deferred payment possible. Without it, an unpaid order would be invisible to your integrations until the money arrived.

Trigger condition

Fire emits order.opened once, at injection, when:
  • order.status === "OPEN"
That is the only condition. Unlike order.completed, there is no payment guardpaymentStatus is typically "PENDING" and that is expected.
order.opened does not fire for orders injected as COMPLETED or CANCELLED. Those orders never “open”: they skip the deferred-payment cycle entirely and produce only order.completed. Emitting order.opened for them would risk dispatching the same order to the kitchen twice.

The order’s life after this event

order.opened is the first of up to three events for the same order. Knowing the sequence matters, because the same order will reach your endpoint more than once:
An order that is cancelled before payment produces order.cancelled instead of order.completed.
If your flow issues a fiscal document on order.opened, the same order will pass through your fiscal node again on order.completed. That second pass is expected and harmless: Fire detects the existing document and returns an idempotent “already invoiced” result instead of issuing a second one. See Deferred payment below.

What’s in trigger.data

trigger.data is the V4 order snapshot — byte-for-byte the same structure order.completed carries, with two differences you should expect: That last row is the one that surprises integrators. Read it carefully.

The declared method is not the charged method

At order.opened nobody has paid, so payments.paymentMethods[] carries the method the POS announced when it created the order — often the marketplace (IFOOD, RAPPI) or a placeholder. transactionStatus is "PENDING" and transactionId is usually empty. When the charge lands, Fire overwrites that array with the real tenders and emits order.completed. Same order, same field, different meaning:
order.opened — declared
order.completed — actually charged
Never treat payments.paymentMethods[] on order.opened as settlement evidence. It is an intention, not a fact. If you need to know what was really collected, wait for order.completed or call Get order, which exposes settlement.

Deferred payment policy

data.policy.deferredPayment is the reason this event exists. It tells you whether this order is allowed to be acted on before payment — cooked, invoiced, dispatched. The policy is resolved once, when the order is injected, from the combination of channel × fulfillment × declared payment method. It is then stamped immutably on the order, and every later event echoes it without recalculating. Two events for the same order always carry an identical policy.
Why immutable? Because the decision must stay auditable. If the store’s configuration changes an hour later, an order already in flight must keep behaving the way it was told to — and you must be able to prove why. Same pattern as store.storeFiscalConfig.
policy is present on every order event (order.opened, order.completed, order.invoiced, order.cancelled), not just this one. An order with eligible: false still carries the block — it just says the answer was no.

Last known state

data.lastKnown is an advisory snapshot of what Fire knew about the order’s kitchen and fiscal state at the moment the event was emitted.
lastKnown is a hint, never a source of truth. It can be stale, and on order.opened it is frequently null simply because nothing has happened yet. Do not gate an irreversible action on it — if you are about to issue a fiscal document, refunds are not a thing you want to discover you needed. Check the real state first, or rely on Fire’s own idempotency.Fire itself follows this rule: its fiscal node re-reads the document state from source before emitting, and ignores lastKnown entirely.
fiscal.status values you may see: pending, processing, authorized, contingency, cancelling, cancelled, rejected, denied, error. Fire also tracks two internal states for orders with no document yet — those are reported as null here rather than leaking bookkeeping into your contract.

Everything else

The remaining blocks — store, client, channel, orderLines, fulfillment, kds, device, operator, marketing, metadata, payments.totals — are identical to order.completed. Rather than duplicate them, see the order.completed field reference.

Common pitfalls

It is not. Nobody paid. Counting order.opened in revenue reporting will inflate your numbers and double-count once order.completed arrives for the same orderId.
Pre-paid orders (kiosk, web checkout) are injected already COMPLETED and never emit it. If your integration depends on order.opened firing first, it will silently skip those orders. Subscribe to both.
See above. On order.opened it is what the POS declared, not what was collected.
The same orderId reaches you on order.opened and again on order.completed. That is by design. Make your handler idempotent per (orderId, action), not per orderId.

Next

data.fiscalRepresentation

The fiscal numbering the point of sale obtained before injecting the order: it charges, requests the identifiers, prints the receipt, and only then injects. That is why it travels on the order and not in a separate fiscal event — by the time the order is born, this already happened.
The presence of this block does NOT mean the document is authorized. These are the numbers printed at the till; the tax authority’s verdict is in lastKnown.fiscal.status. A ticket that says “authorized” just because the block is present states something that may never have happened.
The key always travels. It arrives as null when numbering was not attempted — aggregators, countries without fiscal representation, or merchants with numbering turned off — and carries the block when it was. Carrying the block means numbering was attempted, not that it succeeded: numberingStatus tells you how the attempt ended, and failure why, when it did not end well. Branch on the value, not on the key’s presence:
The authority’s verdict does not alter it. What the customer took home printed does not change because the authority later authorizes or rejects — that is what lastKnown.fiscal is for, and that is what does move. What does replace it is a new document. The block carries the order’s current fiscal document. While there was only one, it was always the invoice; when a cancellation produces a credit note, the note is what sits on top — documentType says which one — and the invoice moves down into history, whole and with its own authority identifiers. It is not lost: it moves. compensates points at it by number, so the relationship stays explicit.

When numbering fails

A sale can be charged and end up with no fiscal receipt. That case travels too, and you must handle it: the identifiers come back null and the reason is in failure.
Branch on failure.scope:
  • TECHNICAL — print “pending” and carry on. It may resolve on its own.
  • FUNCTIONAL — some data is wrong and retrying will not fix it. Needs correction.
lastKnown.fiscal.sourceEvent now reports real provenance. It used to be derived from the status, so a processing seeded at injection was reported as fiscal.callback even though no callback had occurred. That case now says order.injected. If you branch on this field, account for the new value.