Skip to main content
A fulfillment workflow is the graph that starts when a customer places an order. Each node is a step (Paid, Injecting, Prepared, …). Edges decide what happens next. Fire spark always tells the order owner what changed — you do not add a notify action.
Create the fulfillment option first (Delivery, Pickup, Dine-in, …), then edit its workflow in the dashboard. Types are explained in Fulfillment.

Create a fulfillment

  1. In the dashboard, open Fulfillment and create an option.
  2. Pick a type. The type is the customer promise, not the channel.
  3. Choose a preset (or start from the default graph for that type).
  4. Assign channels so the option appears on app, web, or kiosk.
Saving without a graph is possible, but the storefront needs a workflow to place orders.

Presets and the graph

Presets give you start, state, join, and terminal nodes plus the edges between them. Prepaid presets mark INJECTED as Release checkout so the overlay waits until POS injection has finished. You can add or remove nodes after applying a preset. Exactly one start node is required. Terminals are outcomes (FULFILLED, VOIDED, REVERSED, REFUNDED).

Nodes, edges, webhooks, callbacks

Open a node to set: Edges can require a condition (prepaid vs cash). Manual advance from the POS or dashboard moves the order when the graph pauses (WAITING).

order.changed

Each node is persisted and its callbacks run before anything is notified. The next step depends on the graph:
  • The run waits for a manual advance: the row is saved as WAITING and realtime released goes out, then order.changed and the merchant webhooks.
  • The next node is automatic: order.changed and the webhooks go out, then that next node is entered.
  • The node is terminal: the run is finalized, then order.changed and the webhooks go out.
order.changed for the node that was just entered is emitted with the webhook delivery, after callbacks. It is not emitted when the order first steps into the node. A failed callback does not send that node’s webhook. The terminal webhook (VOIDED or REVERSED) is sent only after that terminal is persisted and the run is finalized. An advance that arrives while a webhook is still being delivered stays queued and runs after that delivery returns, so a PAID webhook finishes before INJECTING is processed. PATCH /orders/{id}/status claims the row while it is WAITING, queues the advance, and returns without waiting for the next node. The order in the response can still be the snapshot from before this advance. A 409 means the row was not WAITING. The owner of the order is the only storefront client that receives the realtime event (topic sf:{merchant}:{env}:c:{customer_id}). Subscribe with @fire-spark/realtime:
On every SUBSCRIBED (first connect and reconnect) the SDK calls Get order fulfillment and delivers that snapshot through the same on.changed handler. A late client does not need the missed event — the snapshot is enough. Token minting is documented at Issue realtime token.

Payload

The event and the fulfillment DTO share checkout_state and error. The event also includes callbacks[] — the finished callback_run for each node that had invoke_callback (response, errors[], timing). You do not get a separate event per retry or when the node is first entered. An INJECTING success typically has { order_authorization_id, injection_order_id } in response. If the serialized payload exceeds 64 KB, response / body become { truncated: true }. Read the snapshot for the full objects. error is a short summary for the checkout overlay. Full failures stay in callbacks[].errors. The event does not include webhook URLs or secrets.

Release checkout

At most one node can have Release checkout. Zero is valid: checkout then finishes as soon as the order is accepted (today’s flow, no socket). The overlay waits until checkout_state leaves pending. The server computes that value:
  • No release node → released
  • Run FAILED, or order VOIDED / REVERSED / REFUNDED → failed
  • Run WAITING / SUCCESS, or the current node is a terminal → released
  • The release node was entered and its callback (if any) finished without error → released
  • Otherwise → pending
In other words: the overlay releases when the flagged step finishes or the run pauses for the store or the run reaches a terminal — whichever comes first. A COD branch that pauses at WAITING without a flag still releases. Prepaid presets flag INJECTED so the overlay waits until injection has completed. Saving without a flag shows a warning: checkout finishes as soon as the order is accepted. You can save anyway. If you toggle the flag on a second node, the dashboard asks you to move it. Confirming clears the previous node.

Runtime

Checkout overlay
  • checkout_release === false on List fulfillment → place over HTTP, then success, as today. No socket.
  • true → listenOrder starts in onMutate, overlay stays pending after HTTP success.
  • Event or snapshot released → success, then order detail.
  • failed → error message, then order detail (the cart was already replaced; staying on checkout invites a second payment).
  • HTTP error (order not placed) → failed, back to checkout.
  • 60 s still pending → “Still confirming”, then order detail.
Order detail The profile screen listens only while run_status is PROCESSING or WAITING. Each event invalidates the order query. The socket closes on unmount or when the refetch returns SUCCESS / FAILED. POS / dashboard Manual terminal (cancel, refund, reverse) and pause-for-advance emit the same event. warnNoEdge does not emit by itself — pause follows and does.

Fulfillment

Types and the customer promise

Placing orders

Cart to kitchen ticket

Realtime token

Topics and events

Get order fulfillment

Snapshot with checkout_state