Documentation
Webhooks
Receive signed HTTP notifications in your systems every time a document changes in facturifirma.ro.
Registering a webhook
From the app: the Webhooks page (admin menu) — add the target URL and a signing secret. The secret is write-only: it cannot be read back after saving.
Via the API, with an admin API key:
curl -X POST https://app.facturifirma.ro/api/v1/webhooks \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/webhook", "secret": "<SIGNING_SECRET>"}'Events
A single event type is currently delivered:
| document.changed | any change to a document — create, edit, issue, payment, reversal, cancel or delete, for every document type (invoice, proforma, receipt, delivery note, order, credit note). |
New event types will be added over time; treat the "event" field as extensible and ignore unknown types.
Payload
Every delivery is a POST with a JSON body:
POST https://example.com/webhook
{
"event": "document.changed",
"documentId": "8b1f7c02-4c33-4a6e-9f0d-2f6f6f0a9d11",
"at": "2026-07-15T09:30:00Z"
}Headers
Every request carries the event type and the body signature:
Content-Type: application/json
X-Facturifirma-Event: document.changed
X-Facturifirma-Signature: sha256=<hex(HMAC-SHA256(secret, body))>Verifying the signature
The signature is HMAC-SHA256 over the raw request body with your signing secret, formatted "sha256=<hex>". Compare it in constant time:
import crypto from "node:crypto";
function verify(rawBody, signatureHeader, secret) {
const expected = "sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signatureHeader), Buffer.from(expected));
}Delivery and retries
Deliveries are dispatched from a job queue with automatic retries (at-least-once delivery, no ordering guarantee). Your endpoint must answer with a 2xx status; anything else counts as a failure and is retried.
Security
Only absolute http(s) URLs resolving to public addresses are accepted (SSRF guard). The signing secret is encrypted at rest and never returned by the API.