e-bon
e-bon.ro
Architecture

How receipt emission works

Understand how e-bon dispatches a print command to your device, when it falls back to a push wakeup, how long each stage takes, and how you receive the final result.

When you ask e-bon to print a fiscal receipt, the request travels from your server, through the e-bon API, and out to the AMEF device sitting on the merchant's counter. This page explains that journey from your perspective: what the API does on your behalf, how long each stage takes, and how you find out whether the receipt was printed.

The same pipeline runs for every fiscal command — print_receipt, print_reversal_receipt, void_receipt, x_report, z_report, cash_in, cash_out. Receipts are just the highest-volume case.

Send the print command

Issue a single HTTP request to dispatch a print job:

POST /api/v1/devices/{deviceId}/commands
Content-Type: application/json
Idempotency-Key: <your-key>

{ "type": "print_receipt", "payload": { /* receipt body */ } }

The API responds immediately with 201 Created and status: "pending". The actual print runs in the background — your client is never blocked while the device works.

Two endpoints touch a receipt and they do different things:
  • POST /api/v1/devices/{deviceId}/commands with type: "print_receipt" is the trigger. The device prints as a side effect.
  • POST /api/v1/receipts is persistence-only. It stores a receipt that was already printed (typically posted back by the device app). It does not invoke the printer.
If you want to print, use the commands endpoint. If you want to record a receipt that exists outside e-bon, use the receipts endpoint.

Reach the device

The API uses a two-tier strategy to reach the device. You don't choose between them — the system picks automatically based on the device's current connection state.

Try the live connection first

If the device app is connected over WebSocket, the API hands the command to it directly. This is the normal path and usually completes in under a second.

Fall back to a push wakeup

If the device is not currently connected (mobile network blip, app backgrounded), the API sends a high-priority push notification through Firebase Cloud Messaging (FCM). The app wakes up, opens its connection, executes the command, and reports the result back.

Sweep stuck commands with a safety net

A background sweeper runs every 30 seconds. Any command that has been stuck in pending or sent for more than 180 seconds is forced to timeout so it never hangs forever.

You see all of this as a single status flow on the command document — you don't need to know which path was taken.

Plan for the timing budget

StageLimitWhat it means for you
Live WebSocket attempt30 sIf the device replies in time, you get the result on the same channel.
Push wakeup + result60 sIf the device is offline, the API waits up to 60 s for a result after sending the push.
Safety sweep180 sA command that hasn't reached a terminal state in 180 s is forced to timeout.

If you build a synchronous user flow on top of these calls, design for up to ~3 minutes worst case before a final status appears. In practice, the live path completes in under a second.

Receive the result

Two ways to find out what happened, and you can use both:

Subscribe to these events when you create your webhook endpoint:

  • command.completed — the device executed the command successfully.
  • command.failed — the command failed for any reason within the live or push attempt.
  • command.timeout — the 180 s safety sweep terminated the command.
  • receipt.created — emitted shortly after command.completed for a print_receipt, when the device app posts the printed receipt back to e-bon.

Webhooks are the right primitive for fiscal flows because they push a result the moment it is known.

Handle each outcome

Final statusMeaningWebhook eventYour action
completedThe device printed the receipt and reported a fiscal ID.command.completed, then receipt.createdRecord result.fiscalId and result.fiscalDate against your order.
failed (live path)The device responded with an error — paper out, fiscal memory full, ANAF rejection, driver error, etc.command.failedRead errorCode and errorMessage. Surface the cause to the operator, then retry with a fresh command.
failed (push path)The push could not be sent (no FCM token registered for the device, FCM error).command.failedVerify the device is registered and online, then retry.
timeout (push path)60 s elapsed after the push wakeup with no result. The receipt may have printed but the device did not confirm in time.command.failedCheck the device. Print may need to be re-issued — verify before retrying to avoid duplicates.
timeout (safety sweep)180 s elapsed with no terminal status.command.timeoutSame as above. Inspect the device, then decide whether to retry.
A timeout does not guarantee the receipt was not printed. The device may have printed successfully but lost connectivity before reporting the result. Always confirm with the operator before re-issuing a print command — duplicate fiscal receipts cannot be undone, only reversed with a separate command.

Avoid duplicate prints

Always send an Idempotency-Key header on POST /commands. If your client retries the same request (network blip, restart), e-bon returns the original response instead of issuing a second print. See Idempotency & retries for the full behaviour, including key TTL and replay rules.

Distinguish receipt and command events

A successful print_receipt produces two webhook events, in this order:

  1. command.completed — fires the moment the device confirms execution. Includes the fiscal ID in result.
  2. receipt.created — fires shortly afterwards (typically within the same second) when the device app posts the full receipt body back to e-bon for storage.

If you only care about whether the print succeeded, command.completed is enough. If you need the full itemised receipt — line items, VAT breakdown, payment splits — listen for receipt.created and read it via GET /api/v1/receipts/{id}.

Continue your integration