Overview
Webhooks are HTTP callbacks that notify your application when specific events occur, such as:- Document received
- Document sent
- Document send failed
- Document receive failed
Setting Up Webhooks
1. Create a Webhook
2. Configure Your Endpoint
Your webhook endpoint should:- Accept POST requests
- Use HTTPS with a valid SSL/TLS certificate (recommended; self-signed certificates are not supported)
- Support HTTP Basic Authentication via URL format:
https://username:[email protected]/endpoint(optional) - Return a 200 OK response quickly
- Handle retries gracefully
- Verify webhook signatures
- Protocol: HTTP and HTTPS are both supported (HTTPS strongly recommended for production)
- Certificate: If using HTTPS, SSL/TLS certificate must be valid and issued by a trusted Certificate Authority
- Self-signed certificates: Not supported - webhook delivery will fail with HTTPS
- Authentication: You can include HTTP Basic Authentication credentials directly in the URL:
- URL format: Standard URL format with optional path, query parameters, and authentication
Webhook Security
Signature Verification
Webhook requests are signed using HMAC-SHA256 to ensure authenticity and integrity. The signature is included in theX-Signature header of each request.
How the Signature is Computed
The signature is computed over the entire webhook event payload (not just thedata field). This ensures the integrity of all webhook data including the event ID, timestamp, type, and content.
- The complete webhook event is serialized to JSON with sorted keys
- The JSON string is encoded to UTF-8 bytes
- An HMAC is created using your webhook secret and the SHA-256 algorithm
- The resulting signature is formatted as
sha256={hexadecimal_signature}
Example
Given this complete webhook event payload:- Newlines are represented as
\n(literal backslash-n, not actual line breaks) - Unicode characters like emojis are escaped as
\u26a1\ufe0f(for ⚡️) - Quotes inside the
textfield are escaped as\"
- Sorted keys (
sort_keys=Truein Python’sjson.dumps()) - No extra whitespace (compact format, not pretty-printed)
- Proper escaping of special characters (standard JSON encoding)
json.dumps(payload, sort_keys=True) in Python, JSON.stringify() in JavaScript, or equivalent functions in other languages.
With the webhook secret "secret", this produces the signature:
Computing the Signature
All webhook requests from e-invoice.be include a cryptographic signature that allows you to verify the request’s authenticity. This prevents unauthorized parties from sending fake webhook events to your endpoint. Here’s the exact code used by e-invoice.be to compute webhook signatures:-
Serialize the entire webhook event: The complete webhook event (including
id,tenant_id,created_at,type,data, andtextfields) is serialized to JSON withsort_keys=Trueto ensure consistent key ordering. This is critical because{"a": 1, "b": 2}and{"b": 2, "a": 1}are semantically identical but would produce different signatures without key sorting. - Convert to bytes: The JSON string is encoded to UTF-8 bytes, which is required for the HMAC operation.
- Generate HMAC: Using your webhook secret as the key, an HMAC (Hash-based Message Authentication Code) is computed using the SHA-256 hashing algorithm. This creates a cryptographic signature that only someone with your secret can reproduce.
-
Format the result: The hexadecimal digest is prefixed with
sha256=to indicate the algorithm used, matching the format in theX-Signatureheader.
data field. This ensures the integrity and authenticity of all event information including the event ID, timestamp, type, and all data.
Verifying the Signature
To verify the signature in your webhook handler:- Extract the signature from the
X-Signatureheader (format:sha256={hex_digest}) - Read the raw request body as a string (the complete JSON webhook event)
- Compute the HMAC-SHA256 signature using your webhook secret and the raw request body
- Compare the computed signature with the one in the header using a constant-time comparison
- Always verify the signature before processing the webhook
- Use constant-time comparison to prevent timing attacks
- The signature covers the entire raw request body (all fields:
id,tenant_id,created_at,type,data,text) - Do not parse the JSON and re-serialize it - use the exact raw request body as received
- If the signatures match, the webhook request is authentic and hasn’t been tampered with
Available Events
The following webhook events are supported:document.received: A new document is successfully received via Peppol or other channelsdocument.received.failed: A document failed to be received/processeddocument.sent: A document is successfully sent via Peppol or other channelsdocument.sent.failed: A document failed to send (e.g., validation error, network error, Peppol transmission failure)
Webhook Payload
When a webhook is triggered, it sends a POST request to your configured URL with the following structure:data object only contains the document_id. You can use this ID to fetch additional document details via the API (GET /api/documents/{document_id}).
Headers
Each webhook request includes the following headers:X-Signature: HMAC-SHA256 signature for verifying authenticity (format:sha256=...)X-Event-Type: The event type (e.g.,document.sent)Content-Type:application/jsonUser-Agent:e-invoice-be-webhook-service
Event Data Structure
Thedata object contains event-specific information:
- For all document events (
document.received,document.sent,document.sent.failed,document.received.failed):
document_id can be used to retrieve full document details:
Complete Example Requests
Success Event Example
Here’s what a successfuldocument.sent webhook HTTP request looks like:
Failure Event Example
Here’s what a faileddocument.sent.failed webhook HTTP request looks like:
FAILED and may contain error information. You can retry sending a failed document by calling POST /api/documents/{document_id}/send again.