Skip to main content
You’re viewing the current (v2) contract. It adds a structured cancellation motive to the cancellation block — cancellationType (stable code), cancellationGroup (category) and cancellationNote (free text), always present (null when empty) — on top of the v1 order snapshot.
order.reversed fires when SEFAZ confirms the cancellation of a fiscal document (NFC-e or NF-e) that had been previously authorized. It is the SEFAZ-confirmed counterpart to order.cancelled: the order is cancelled first inside Fire (firing order.cancelled), Fire then requests cancellation at SEFAZ via your fiscal provider, and order.reversed fires only when SEFAZ stamps the cancellation protocol.

Trigger condition

Fire emits order.reversed once per fiscal cancellation, the first time all of these are true:
  • The order is in a store with fiscal billing enabled (storeFiscalConfig.enabled === true)
  • A fiscal document had been previously authorized (i.e. order.invoiced was emitted earlier)
  • A cancellation request was submitted to your fiscal provider
  • your fiscal provider reports the fiscal authority’s confirmation of the cancellation

What’s in trigger.data

Same V4 order snapshot as order.cancelled — including the same cancellation audit block — with an extra sefazCancellation sub-object inside cancellation.metadata.fiscal. That’s the only structural difference. status is "CANCELLED". The order is the same one referenced by the earlier order.cancelled event for the same orderId.

Example — real production payload (BR, sanitized)

data.cancellation.metadata.fiscal.sefazCancellation reference

This is the only block that’s unique to order.reversed. Every other field is shared with order.cancelled — see that page for the cancellation audit fields.
object
SEFAZ confirmation of the cancellation. Present only after SEFAZ has stamped the cancellation protocol.

Lifecycle

For a Brazilian fiscal-enabled order, expect the four events above. For Brazilian orders without fiscal authorization (because the order was cancelled before fiscal emission, or fiscal was disabled), only order.cancelled fires — no order.reversed.

Handler example

Common pitfalls

  • Don’t process order.reversed without order.cancelled first. They fire in order in normal flow, but out-of-order delivery is possible. If you receive order.reversed for an orderId you don’t have a cancellation record for, log it and create the record from this event’s cancellation block — don’t drop the event.
  • sefazCancellation.date and cStat may be null in sandbox. Don’t make production-readiness depend on them being present in dev environments.
  • The cancellation XML URL is distinct from the original document XML. Make sure your archival logic stores both — you’ll need them for audit.
  • Same cancellation.cancellationId as order.cancelled. Both events for the same cancellation share the cancellation ID — useful as a join key when correlating the two events in your system.
  • No event for failed SEFAZ cancellation. If SEFAZ rejects the cancellation request, no event fires. Monitor the dashboard’s executions log for those cases.

order.cancelled

Fires before this event — the cancellation itself.

order.invoiced

The earlier event that established the fiscal document being cancelled here.

data.fiscalRepresentation

On order.reversed this block matters especially: these are the numbers of the receipt being annulled. The annulment does not change them — what changes is lastKnown.fiscal.status, which becomes cancelled.
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.