e-bon
e-bon.ro
Referință API

Evenimente webhook

Primește notificări în timp real despre evenimentele fiscale — bonuri, comenzi, dispozitive și rapoarte — prin callback-uri HTTPS semnate.

Evenimente webhook

Webhook-urile permit POS-ului, ERP-ului sau back-office-ului tău să reacționeze în timp real la evenimentele fiscale din e-bon: a fost emis un bon fiscal, un dispozitiv s-a deconectat, s-a generat un raport Z. e-bon livrează fiecare eveniment printr-un POST HTTPS semnat către un URL controlat de tine.

Abonează-te la evenimente webhook

Configurează endpoint-urile din Portal:

Deschide pagina de webhook-uri

În Portal, accesează Setări → Webhook-uri și apasă Adaugă endpoint.

Introdu URL-ul și alege evenimentele

Lipește URL-ul HTTPS care va primi livrările, apoi bifează evenimentele la care vrei să te abonezi (de exemplu receipt.created, device.offline, report.generated).

Salvează și copiază secretul de semnare

După salvare, e-bon afișează secretul de semnare (whsec_…) o singură dată. Copiază-l în secret manager-ul tău — îți va trebui pentru a verifica semnăturile.

Trimite o livrare de test

Apasă Trimite test pentru a declanșa un eveniment webhook.test și a confirma că endpoint-ul tău întoarce 2xx.

Poți administra webhook-urile și programatic prin API-ul Webhooks.

Inspectează plicul evenimentului

Fiecare livrare este un singur obiect JSON cu aceeași formă exterioară:

{
  "id": "8f3a9d3e-1b8c-4f02-9b2e-1234567890ab",
  "type": "receipt.created",
  "createdAt": "2026-04-23T08:09:55.123Z",
  "orgId": "acme_corp",
  "data": { /* specific evenimentului, vezi mai jos */ }
}
id
string required
UUID-ul evenimentului. Stabil pe livrare — trimis și în antetul X-EBon-Delivery-Id. Folosește-l pentru a deduplica reîncercările.
type
string required
Numele evenimentului, de exemplu receipt.created. Vezi referința de evenimente.
createdAt
string required
Marcaj de timp ISO 8601 capturat la momentul emiterii evenimentului.
orgId
string required
Organizația căreia îi aparține evenimentul.
data
object required
Payload specific evenimentului. Forma depinde de type — vezi fiecare eveniment mai jos.

Citește antetele HTTP

Fiecare livrare este un POST application/json cu următoarele antete:

AntetSemnificațieExemplu
Content-TypeÎntotdeauna application/json.application/json
X-EBon-SignatureHMAC SHA-256 al corpului brut al cererii, prefixat cu sha256=.sha256=4c8f…3a9d
X-EBon-EventTipul (type) evenimentului (oglindă a corpului).receipt.created
X-EBon-Delivery-IdID-ul evenimentului (oglindă a corpului). Folosește-l drept cheie de idempotență.8f3a9d3e-1b8c-4f02-9b2e-1234567890ab
X-EBon-TimestampMarcaj de timp ISO 8601 al acestei încercări de livrare.2026-04-23T08:09:55.300Z

e-bon așteaptă până la 10 secunde un răspuns de la endpoint-ul tău și tratează orice status 2xx ca succes. Orice altceva este o eroare și se programează pentru reîncercare.

Verifică semnătura webhook-ului

Semnătura este HMAC SHA-256 al corpului brut al cererii, codificată hex și prefixată cu sha256=. Verifică-o întotdeauna pe baza octeților bruți primiți — nu re-serializa niciodată JSON-ul parsat, fiindcă spațiile albe și ordinea cheilor ar strica comparația.

Express handler
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const app = express();

// Capturează corpul brut pentru verificarea semnăturii.
app.use(express.json({
  verify: (req, _res, buf) => {
    (req as unknown as { rawBody: Buffer }).rawBody = buf;
  },
}));

app.post('/webhooks/e-bon', (req, res) => {
  const rawBody = (req as unknown as { rawBody: Buffer }).rawBody;
  const sent = req.header('X-EBon-Signature') ?? '';
  const expected = `sha256=${createHmac('sha256', process.env.EBON_WEBHOOK_SECRET!)
    .update(rawBody)
    .digest('hex')}`;

  const a = Buffer.from(sent);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    res.status(401).end();
    return;
  }

  // Semnătura OK — req.body este plicul.
  res.status(202).end();
});
Folosește mereu o comparație în timp constant: crypto.timingSafeEqual în Node, hash_equals în PHP sau hmac.compare_digest în Python. Compararea cu === sau == lasă să scape informații de timing care permit unui atacator să ghicească semnătura octet cu octet.

Reacționează la evenimente

Forma plicului se repetă pentru fiecare eveniment. Secțiunile de mai jos descriu ce declanșează fiecare eveniment, payload-ul data și ce să faci cu el.

receipt.created

Se declanșează după ce un bon fiscal este persistat în e-bon (emis din API, din aplicația de casă sau dintr-un POS conectat).

Când îl folosești: sincronizează înregistrarea fiscală în ERP, trimite bonul către un canal vizibil clientului (email, aplicație) sau actualizează dashboard-urile de raportare.

data: {
  id: string;
  orgId: string;
  deviceId: string;
  total: number;
  currency: string;
  items: Array<{ name: string; price: number; quantity: number; vatRate: number }>;
  payments: Array<{ method: string; amount: number }>;
  operatorId: string;
  fiscalId?: string;
  fiscalDate?: string;
  customerCif?: string;
  qrCode?: string;
  source: 'api' | 'pos' | 'app';
  createdAt: string;
}
exemplu
{
  "id": "8f3a9d3e-1b8c-4f02-9b2e-1234567890ab",
  "type": "receipt.created",
  "createdAt": "2026-04-23T08:09:55.123Z",
  "orgId": "acme_corp",
  "data": {
    "id": "rcp_abc123",
    "orgId": "acme_corp",
    "deviceId": "dev_xyz",
    "total": 4250,
    "currency": "RON",
    "items": [{ "name": "Espresso", "price": 850, "quantity": 5, "vatRate": 19 }],
    "payments": [{ "method": "card", "amount": 4250 }],
    "operatorId": "op_42",
    "fiscalId": "FISC-2026-000123",
    "source": "api",
    "createdAt": "2026-04-23T08:09:55.000Z"
  }
}

Vezi API-ul Bonuri pentru schema completă.

command.completed

Se declanșează când o comandă fiscală se finalizează cu succes pe AMEF — de exemplu un bon a fost tipărit sau s-a generat un raport X/Z.

Când îl folosești: marchează comanda originară ca fiscalizată, atașează fiscalId-ul întors la înregistrările tale sau pornește fluxuri downstream.

data: {
  id: string;          // id-ul comenzii
  deviceId: string;
  type: CommandType;   // 'print_receipt' | 'x_report' | 'z_report' | …
  result: CommandResult;
  orgId: string;
}
exemplu
{
  "id": "evt_…",
  "type": "command.completed",
  "createdAt": "2026-04-23T08:10:01.000Z",
  "orgId": "acme_corp",
  "data": {
    "id": "cmd_abc123",
    "deviceId": "dev_xyz",
    "type": "print_receipt",
    "result": { "receiptId": "rcp_abc123", "fiscalId": "FISC-2026-000123" },
    "orgId": "acme_corp"
  }
}

CommandType și CommandResult sunt documentate pe API-ul Commands.

command.failed

Se declanșează când o comandă fiscală este respinsă de AMEF sau de handlerul dispozitivului — fără hârtie, imprimantă deconectată, eroare de validare etc.

Când îl folosești: alertează echipa de operațiuni, reîncearcă comanda după ce remediezi cauza (retryable: true) sau afișează eroarea către operator.

data: {
  id: string;          // id-ul comenzii
  deviceId: string;
  type: CommandType;
  error: string;       // mesaj lizibil
  errorCode: ErrorCode;
  retryable: boolean;
  orgId: string;
}
exemplu
{
  "id": "evt_…",
  "type": "command.failed",
  "createdAt": "2026-04-23T08:10:02.000Z",
  "orgId": "acme_corp",
  "data": {
    "id": "cmd_abc123",
    "deviceId": "dev_xyz",
    "type": "print_receipt",
    "error": "Paper jam",
    "errorCode": "DEVICE_ERROR",
    "retryable": true,
    "orgId": "acme_corp"
  }
}

Lista completă de valori errorCode este pe prezentarea API-ului.

command.timeout

Se declanșează când o comandă din coadă nu primește răspuns de la dispozitivul ei în fereastra configurată.

Când îl folosești: tratează-l ca o eroare temporară — dispozitivul ar putea fi deconectat. Verifică evenimentele device.online / device.offline înainte să reîncerci.

data: {
  id: string;
  deviceId: string;
  type: CommandType;
  error: string;
  errorCode: ErrorCode;
  retryable: boolean;
}
exemplu
{
  "id": "evt_…",
  "type": "command.timeout",
  "createdAt": "2026-04-23T08:15:00.000Z",
  "orgId": "acme_corp",
  "data": {
    "id": "cmd_abc123",
    "deviceId": "dev_xyz",
    "type": "print_receipt",
    "error": "Command timed out waiting for device reply",
    "errorCode": "COMMAND_TIMEOUT",
    "retryable": true
  }
}

device.online

Se declanșează când un dispozitiv fiscal se reconectează.

Când îl folosești: golește comenzile din coadă, șterge avertismentele „dispozitiv deconectat” din dashboard sau notifică operatorul.

exemplu
{
  "id": "evt_…",
  "type": "device.online",
  "createdAt": "2026-04-23T08:00:00.000Z",
  "orgId": "acme_corp",
  "data": { "id": "dev_xyz", "status": "online", "orgId": "acme_corp" }
}

device.offline

Se declanșează când un dispozitiv se deconectează.

Când îl folosești: alertează operatorul, suspendă trimiterea automată de comenzi sau bascuează pe un dispozitiv secundar.

exemplu
{
  "id": "evt_…",
  "type": "device.offline",
  "createdAt": "2026-04-23T08:30:00.000Z",
  "orgId": "acme_corp",
  "data": { "id": "dev_xyz", "status": "offline", "orgId": "acme_corp" }
}

report.generated

Se declanșează în paralel cu command.completed ori de câte ori comanda finalizată a fost un raport X sau Z.

Când îl folosești: arhivează raportul, trimite totalurile de final de zi în sistemul de contabilitate sau pornește un job zilnic de reconciliere.

data: {
  commandId: string;   // atenție: commandId, nu id
  deviceId: string;
  type: CommandType;   // 'x_report' | 'z_report'
  result: CommandResult;
  orgId: string;
}
exemplu
{
  "id": "evt_…",
  "type": "report.generated",
  "createdAt": "2026-04-23T22:00:00.000Z",
  "orgId": "acme_corp",
  "data": {
    "commandId": "cmd_abc123",
    "deviceId": "dev_xyz",
    "type": "z_report",
    "result": { "reportId": "rpt_…", "totals": { "gross": 125000, "net": 105042 } },
    "orgId": "acme_corp"
  }
}

Vezi API-ul Rapoarte pentru înregistrarea de raport completă.

webhook.test

Trimis doar atunci când apeși Trimite test în Portal sau apelezi endpoint-ul de livrare de test. Payload-ul este fix:

exemplu
{
  "id": "evt_…",
  "type": "webhook.test",
  "createdAt": "2026-04-23T08:10:00.000Z",
  "orgId": "acme_corp",
  "data": { "test": true, "message": "This is a test event from e-bon." }
}

Folosește-l pentru a confirma că endpoint-ul tău este accesibil și că verificarea semnăturii funcționează cap-coadă.

Gestionează reîncercările

Când endpoint-ul tău întoarce un status non-2xx, depășește timeout-ul sau este inaccesibil, e-bon reîncearcă livrarea cu backoff exponențial.

ÎncercareÎntârziere până la reîncercare
1 → 21 minut
2 → 35 minute
3 → 430 de minute
4 → 52 ore
5 → 612 ore

După a 5-a încercare eșuată, livrarea este marcată failed și nu mai este reîncercată.

Dacă un webhook acumulează 20 de livrări eșuate consecutive, e-bon dezactivează automat abonamentul. Reactivează-l din Setări → Webhook-uri în Portal după ce ai remediat endpoint-ul.

Poți parcurge istoricul livrărilor (status, cod HTTP, extras din răspuns, număr de încercări) din Setări → Webhook-uri → Livrări sau prin GET /api/v1/org/webhooks/{id}/deliveries. Primele 500 de caractere ale răspunsului tău sunt stocate pe fiecare înregistrare de livrare.

Aplică bunele practici

  • Răspunde rapid. Confirmă cererea cu un status 2xx imediat ce ai validat semnătura; pune munca grea (scrieri în DB, apeluri către API-uri downstream) pe un worker de fundal. Orice depășește timeout-ul de 10 secunde este tratat ca eșec.
  • Fii idempotent. Folosește X-EBon-Delivery-Id drept cheie de deduplicare — o livrare reîncercată are același id. Un simplu INSERT … ON CONFLICT DO NOTHING îți păstrează handlerul în siguranță.
  • Verifică pe corpul brut. Nu recalcula niciodată HMAC-ul peste un JSON re-serializat; spațiile albe și ordinea cheilor contează. Folosește o comparație în timp constant.
  • Stochează secretul într-un secret manager. Valoarea brută whsec_… este afișată doar la creare și la rotație. Ține-o în variabile de mediu sau într-un vault, niciodată în repo.
  • Rotește când ai dubii. Folosește Setări → Webhook-uri → Rotește secretul (sau POST /api/v1/org/webhooks/{id}/rotate-secret) ca să generezi un secret nou dacă există suspiciunea că cel actual a fost compromis.

Continuă explorarea

  • API-ul Webhooks — creează, actualizează, rotește secretele, trimite livrări de test, parcurge istoricul livrărilor.
  • Prezentare API › plicul de eroare — valorile errorCode care apar în command.failed și command.timeout.
  • SDK › evenimente — alternativa server-sent events când ai nevoie de un canal push fără un endpoint HTTPS public.