Nepotrivire de semnătură HMAC
Când endpoint-ul tău calculează un HMAC diferit de cel pe care e-bon l-a trimis în X-EBon-Signature, aproape orice cauză e una din trei: ai re-serializat body-ul JSON înainte să-l hash-uiești, folosești secretul anterior după o rotație sau folosești pur și simplu secretul greșit (de ex. secretul webhook-ului de test pe webhook-ul de producție).
X-EBon-Signature-ul primit, ca să-i poți compara byte cu byte.Cauze probabile
- Body JSON re-serializat — ai parsat cererea ca JSON și apoi ai re-stringificat-o înainte să o hash-uiești. Diferiți serializatori reordonează cheile, schimbă spațierea și reformatează numerele; hash-ul va diferi. Semnătura e peste bytes-urile brute ale body-ului cererii.
- Secret greșit după rotație —
POST /api/v1/org/webhooks/{id}/rotate-secretîntoarce o nouă valoarewhsec_…o singură dată. Dacă ai rotit dar n-ai actualizat secretul stocat de endpoint, fiecare livrare ulterioară va eșua la verificare. - Secret greșit pentru webhook greșit — frecvent când copiezi-lipești secretul webhook-ului de test în endpoint-ul de producție, sau invers. Fiecare abonament are propriul
secret. - Algoritm sau encoding greșit — algoritmul e HMAC SHA-256, digestul e hex cu litere mici, iar valoarea header-ului e literal string-ul
sha256=<hex>. Hex (nu base64), litere mici, o singură linie. - Drift de ceas —
X-EBon-Timestampe marcajul de timp al încercării de dispatch. Dacă respingi livrările pe o fereastră strânsă de skew (de ex. ±2 minute) și ceasul serverului tău s-a derivat, vei respinge cereri altfel valide. Semnătura în sine nu include timestamp-ul — drift-ul de ceas contează doar dacă adaugi peste o verificare anti-replay.
Cum verifici
Reproduce local pe un payload capturat folosind openssl:
# RAW_BODY = bytes-urile exacte primite în body-ul POST-ului, fără reformatare.
# SECRET = valoarea whsec_… a webhook-ului tău.
printf '%s' "$RAW_BODY" \
| openssl dgst -sha256 -hmac "$SECRET" -hex \
| awk '{print "sha256="$2}'
Rezultatul trebuie să fie egal byte cu byte cu valoarea header-ului X-EBon-Signature. Dacă nu e, body-ul pe care l-ai dat lui openssl diferă de cel pe care l-a hash-uit e-bon — aproape întotdeauna din cauza re-serializării.
Pentru a exclude un secret vechi, obține metadatele curente ale abonamentului. Secretul în sine nu e returnat, dar poți confirma enabled, events, url și failureCount:
curl https://api.e-bon.ro/api/v1/org/webhooks/{webhookId} \
-H "Authorization: Bearer <jwt>"
Apoi trimite un eveniment de test controlat și compară cu cererea capturată:
curl -X POST https://api.e-bon.ro/api/v1/org/webhooks/{webhookId}/test \
-H "Authorization: Bearer <jwt>"
Remediere
Capturează body-ul brut înainte ca orice framework să-l parseze
În Express, înregistrează un bodyParser.raw({ type: 'application/json' }) doar pe ruta de webhook, ca req.body să fie un Buffer. În alte framework-uri, găsește echivalentul — rawBody la Fastify, rawBody: true la NestJS, await request.body() la FastAPI. Hash-uiește acel buffer; nu citi întâi request.json() ca să re-stringifici după.
Actualizează secretul stocat după fiecare rotație
De fiecare dată când apelezi POST /api/v1/org/webhooks/{id}/rotate-secret, capturează noua valoare secret din răspuns și scrie-o în managerul tău de secrete (variabilă de mediu, vault, KMS) înainte să sosească următoarea livrare. Secretul anterior încetează să mai funcționeze în clipa în care rotația se termină.
Folosește o comparare în timp constant
Compară cele două string-uri cu crypto.timingSafeEqual (Node), hmac.compare_digest (Python), subtle.ConstantTimeCompare (Go) — niciodată cu ==. Asta previne canalele laterale de timing și prinde și bug-urile subtile de padding cu spații pe care == le-ar accepta în tăcere.
Dacă verifici skew-ul de timestamp, lărgește fereastra sau repară NTP-ul
Dacă respingi livrările cu |now - X-EBon-Timestamp| > N, asigură-te că ceasul serverului tău e în limita N față de UTC prin chronyc tracking sau timedatectl status. Recomandarea platformei e o fereastră de ±5 minute, nu ±30 de secunde — timeout-ul pe încercare e deja 10 s, dar reîncercările pot ajunge mult mai târziu.
openssl folosite de însăși platforma: Evenimente webhook › Verifică semnătura webhook-ului. Forme de payload pe eveniment și header-e HTTP: Evenimente webhook.Tot blocat?
Deschide un caz de suport la support@e-bon.ro sau e-bon.ro/contact cu webhookId-ul, un X-EBon-Delivery-Id capturat, header-ul X-EBon-Signature exact pe care l-ai primit și digestul calculat de endpoint-ul tău (nu e nevoie să trimiți secretul).
Remediază respingerile ANAF de raport (P7B / MF / JE)
Diagnostichează și rezolvă o respingere ANAF pe un raport fiscal — motivele frecvente, cum verifici statusul prin endpoint-ul de rapoarte și calea de remediere pe categorie.
Limită de plan depășită
De ce apare TIER_LIMIT_EXCEEDED, ce limite ale planului o declanșează și cum faci upgrade.