Configure webhooks in the PSB: topics, HMAC SHA256 security and IP whitelisting.
Webhooks are the primary way to receive real-time notifications from the PSB. For every relevant event (an invoice received, a status change, a delivery confirmation) the PSB sends an HTTP POST request to your endpoint with the details of the event.
You register a webhook (a "hook") in the PSB with a URL and a topic. The PSB then sends all events for that topic to your URL. Each event contains the relevant data as a JSON payload.
No UI available: it is currently not possible to configure a hook via the platform user interface. Hooks are set up via the API or via eConnect support. A hook self-service UI is on the roadmap (planned for Q4 2026) and will be part of Control (Platform 2.0): users will then be able to create, edit and delete hooks themselves. Until then, no step-by-step how-to article for manual hook configuration via the API is planned.
Register a hook via the API:
POST /api/v1/hook
The main configuration elements:
InvoiceReceived)Note: avoid rapidly deleting and recreating hooks for the same partyId and topic. Due to race conditions in task processing, this can temporarily result in no active hook, causing events not to be delivered. Wait briefly after deleting a hook before creating a new one, or use an update instead of delete + create.
Want to add an extra topic (e.g. InvoiceReceived) to an existing hook? Update the existing webhook configuration via the regular /hook API so that the desired topic is included in the topics array:
{
"topics": ["InvoiceReceived"]
}
Update the existing hook instead of deleting and recreating it. Deleting and recreating introduces a race condition where events may temporarily not be delivered (see the warning above).
Not self-service for non-technical customers. Adding a topic to an existing hook is a technical operation: the person performing it must know where the webhook should go (the endpoint URL) and understand what a webhook is. A non-technical customer typically cannot do this independently. Involve someone with technical knowledge (customer IT or eConnect support).
InvoiceReceivedInvoiceReceivedErrorInvoiceSentInvoiceSentRetryInvoiceSentErrorInvoiceResponseReceivedInvoiceResponseSentInvoiceResponseSentRetryInvoiceResponseSentErrorMessageLevelStatusReceivedMessageLevelStatusSentOrderReceivedMLS (Message Level Status) is the successor to the older MLR and provides the sending party with feedback on the receipt and processing of a document. MLS is not enabled by default and must be configured per party via the reviews capability in the SMP configuration. Once MLS is enabled, the PSB handles it automatically: as the receiving Service Provider, the PSB sends an MLS message back to the sender after receipt and delivery.
When you send documents via the PSB yourself, you receive MLS feedback from the receiving party as a webhook event on the topic MessageLevelStatusReceived. As the receiving Service Provider, a separate webhook with topic MessageLevelStatusSent follows once the PSB sends out an MLS on your behalf to the sending SP. Use this topic to monitor whether your own self-issued MLS responses are delivered correctly.
MessageLevelStatusSent is not a confirmation of a sent invoice. The topic relates to an MLS the PSB sends in response to a received document (you, as the receiving SP, send the status notification back to the original sender), not to an e-invoice you sent yourself. If you see many MessageLevelStatusSent notifications alongside few sent invoices, check whether the hook listens on the wildcard *sent instead of specifically on InvoiceSent (see Wildcard topics).
The payload includes:
documentIdrefToDocumentIddetails.statusCodeAP (accepted), RE (rejected), AB (acknowledged)details.descriptionTo receive MLS messages, the Peppol hook must contain the mlsType field. The possible values are ALWAYS_SEND (always send MLS back) and FAILURE_ONLY (only on rejections).
Tip: you can retrieve the full MLS document via
GET /api/v1-beta/{partyId}/generic/{documentId}/download, but the webhook payload usually contains sufficient information.
The PSB secures all webhook deliveries with HMAC SHA256 signatures. With every request the PSB sends the header:
X-EConnect-Signature: sha256={signature}
To verify the signature:
X-EConnect-Signature header.Also check the sentOn field in the payload. If this timestamp is older than 5 minutes, reject the request. This prevents replay attacks where an intercepted request is replayed at a later time.
In addition to HMAC verification, the PSB offers extra security layers:
104.40.188.59 and 104.47.148.207)If you have multiple hooks configured, the PSB determines which hook is used based on:
&& clause wins over a shorter filter without that clause. This prevents duplicate delivery without requiring filters to be mutually exclusive.Not every system can expose a webhook endpoint for incoming traffic. Think of on-premise ERP systems or secured networks without inbound internet connectivity. For these situations the PSB offers HTTPS inbound hooks.
Instead of the PSB sending an event to your URL, you reverse the flow: the PSB pushes documents to an internal endpoint using the provided credentials. You configure the action with the httpsin:// protocol:
httpsin://user:pass@inbound?token=$token$
The PSB uses the specified username and password to deliver the document. The $token$ placeholder is automatically replaced by the document token.
HTTPS inbound hooks are specifically designed for environments where the regular webhook API does not work because inbound internet traffic is not possible. In all other cases, standard webhooks are the recommended approach.
By default a hook triggers on every event for the configured topic. With filters you can refine this, so a hook only fires when the event meets a specific condition.
Filters use lambda expression syntax. You add the filter as a property on the hook configuration:
{
"topic": "InvoiceReceived",
"url": "https://jouw-endpoint.nl/webhook",
"filter": "sender == \"0106:12345678\" && verdict.StartsWith(\"acc\")"
}
In this example the hook only triggers if the sender is 0106:12345678 and the verdict starts with acc (accepted).
Common use cases:
verdict.StartsWith("rej"))&& and ||Filters are useful when you have multiple hooks on the same topic, but each system should only receive relevant events. This prevents unnecessary processing.
In addition to specific topics you can also use wildcard patterns to capture multiple event types with a single hook.
Send*InvoiceSent, InvoiceSentError, InvoiceSentRetry, etc.)*sent / *SentSent: InvoiceSent, InvoiceResponseSent, OrderSent, MessageLevelStatusSent, HookSent*ReceivedInvoiceReceived, OrderReceived, InvoiceResponseReceived, etc.)Invoice*Send* is not the same as *sent. Send* matches topics that start with Send; that pattern does not occur among the standard PSB topics. To catch all "sent" events (InvoiceSent, MessageLevelStatusSent, HookSent, etc.), use *sent instead. InvoiceSentRetry and InvoiceSentError do not end in Sent and are therefore not covered by the *sent suffix.
Wildcards are particularly useful for catch-all hooks, for example a monitoring or logging endpoint that should receive all events. You can also combine wildcards with specific topics on other hooks. The priority order ensures that a more specific hook always takes precedence over a wildcard.
Below are common webhook issues and how to resolve them.
sha256=sentOn field older than 5 minutesHookSentError topic (see below); retrieve missed events via the batch endpointX-EConnect-Delivery header (unique UUID) for deduplicationInvoiceReceived), but no hook is registered on that topic (never configured, or removed in the meantime)InvoiceReceived listed in the topics? Without a hook on that topic no HookSentError appears, so the absence of error messages does not prove the hook is activeMessageLevelStatusSent e-mails despite few sent e-invoicesmailto:) or HTTP webhook listens on the wildcard *sent instead of on InvoiceSentInvoiceSent (same change on the mail hook as on the HTTP webhook); see Wildcard topicsInvoiceReceived hook)The PSB can receive a purchase invoice without forwarding it to the receiving party. Topic InvoiceReceived means the PSB has received the invoice; without a hook on that topic, nothing is sent to an endpoint.
HookSentError only exists if a hook is configured that fails to deliver.Fix a missing hook by updating the existing hook instead of deleting and recreating it.
The PSB sends webhook events (e.g. InvoiceReceived when an e-invoice is received) to the configured endpoint. If that endpoint is unreachable — due to a timeout, an SSL error or a DNS error such as No such host is known — the PSB retries with exponential backoff for 5 days (timeout 100 sec per attempt). Each attempt triggers a HookSentRetry event; after 5 days without a 2xx response, HookSentError is issued as the final status and the PSB stops retrying.
Consequence: a received e-invoice will not be delivered to the customer as long as the endpoint is unreachable, without the customer noticing directly. Recommended follow-up:
HookSentError topic so that a definitive delivery failure is actively signalled rather than silently dropped. Consider also subscribing to HookSentRetry for early detection.First line: customer-side downtime vs. technical issue (acceptance and production)
HookSentError, a repeated HTTP 404, or "endpoint is offline"/"acceptance environment is down" can also occur because the customer temporarily switches off the acceptance or production endpoint (development, maintenance). That is not an eConnect platform incident.
HookSentError, endpoint offline, or HTTP 404 on the webhook endpoint configured by the customer. Often visible as the exact text Response status code does not indicate success: 404 (Not Found) (typically a .NET HttpRequestException on the outgoing hook request). This is a 404 from the customer's own endpoint (down or wrong path), not the Peppol PartyId not found error when sending (SMP 404), and not a REST resource 404 from the API's error handling.HookSentError runbook above (DNS/SSL/timeout, logs, monitoring, batch recovery).Take the raw JSON body of the request, compute the HMAC SHA256 hash using the secret you specified when creating the hook and compare it with the value in the X-EConnect-Signature header (after the sha256= prefix). If they match, you know the request came from the PSB and was not modified in transit.
Verify that sentOn is no older than 5 minutes. If the timestamp is too old, reject the request. This prevents replay attacks where a previously intercepted request is resubmitted later, even if the signature is technically valid.
Implement idempotent processing on your side, because the PSB may deliver an event more than once in rare cases. Also return an HTTP 2xx status quickly: any other response counts as an error and triggers a retry. For heavy processing, confirm receipt first and then process via a queue.
HookSentError follows after the PSB has tried for 5 days without a 2xx response. A repeated HTTP 404 or the message that the endpoint is offline is often caused by the customer temporarily switching off the acceptance or production endpoint (development, maintenance) -- this is not an eConnect platform incident. First confirm whether the downtime is intentional and temporary; if so, no further action is needed until the endpoint is reachable again. Missed events can still be retrieved via the batch endpoint within the 5-day retry window.
This is expected behaviour if the mail hook (mailto:) or HTTP webhook listens on the wildcard *sent instead of specifically on InvoiceSent. MessageLevelStatusSent is not a confirmation of an invoice you sent yourself, but of an MLS the PSB sends in response to a received document, and it falls under the *sent pattern together with InvoiceSent, OrderSent and HookSent. If you only want notifications when sending invoices, change the topic configuration to InvoiceSent. InvoiceSentRetry and InvoiceSentError do not end in Sent and are therefore not covered by *sent.
Prefer to retrieve documents in bulk? Check out the batch hook.
View the webhook endpoints