Configure and secure webhooks

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.

How do webhooks work in the PSB?

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.

Creating a webhook

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).

Register a hook via the API:

POST /api/v1/hook

The main configuration elements:

FieldDescriptionurlThe HTTPS endpoint where the PSB sends events totopicThe type of event you want to listen for (e.g. InvoiceReceived)secretA secret key for HMAC signature verification

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.

Commonly used topics
TopicWhenInvoiceReceivedA purchase invoice has been receivedInvoiceSentA sales invoice has been sent successfullyInvoiceSentErrorA sales invoice could not be deliveredInvoiceSentRetryA retry attempt has startedInvoiceResponseReceivedAn Invoice Response (status message) has been receivedMessageLevelStatusReceivedAn MLS status message has been received from the sending partyMessageLevelStatusSentAn MLS status message has been sent successfullyOrderReceivedA purchase order has been received
Message Level Status (MLS)

MLS (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. The payload includes:

FieldDescriptiondocumentIdUnique ID of the MLS messagerefToDocumentIdID of the original documentdetails.statusCodePeppol status: AP (accepted), RE (rejected), AB (acknowledged)details.descriptionExplanation from the receiver

To 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.

Securing webhooks with HMAC

The PSB secures all webhook deliveries with HMAC SHA256 signatures. With every request the PSB sends the header:

X-EConnect-Signature: sha256={signature}
Implementing verification

To verify the signature:

  1. Take the raw JSON payload of the request.
  2. Calculate the HMAC SHA256 hash using your secret key (the same one you provided when creating the hook).
  3. Compare your calculated hash with the value in the X-EConnect-Signature header.
  4. If they match, the request is authentic.
Replay attack prevention

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.

Additional security options

In addition to HMAC verification, the PSB offers extra security layers:

  • IP whitelisting: restrict incoming requests to the PSB production IPs (104.40.188.59 and 104.47.148.207)
  • OAuth webhook authentication: the PSB can authenticate with your endpoint using OAuth2 credentials
  • Mutual SSL: use client certificates for mutual TLS authentication
Priority order

If you have multiple hooks configured, the PSB determines which hook is used based on:

  1. PartyId-level hooks take precedence over environment-level hooks
  2. Specific topics take precedence over wildcards
  3. At equal priority: hook-id as tiebreaker
HTTPS inbound hooks

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.

Hook filters

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:

  • Only trigger on rejections (verdict.StartsWith("rej"))
  • Filter by a specific sender or recipient
  • Combine multiple conditions with && 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.

Wildcard topics

In addition to specific topics you can also use wildcard patterns to capture multiple event types with a single hook.

PatternMatchesSend*All send events (InvoiceSent, InvoiceSentError, InvoiceSentRetry, etc.)*ReceivedAll receive events (InvoiceReceived, OrderReceived, InvoiceResponseReceived, etc.)Invoice*All invoice-related events

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.

Troubleshooting

Below are common webhook issues and how to resolve them.

SymptomCauseSolutionWebhooks not arrivingEndpoint unreachable (timeout 100 sec)HTTPS + valid SSL certificate required; disable CSRF protection on the webhook endpointWebhook rejected as insecureX-EConnect-Signature validation failsCheck HMAC SHA256 calculation: payload × secret → hex string with prefix sha256=Relay attack blockedsentOn field older than 5 minutesEndpoint processes too slowly or clock is wrong"No such host is known" (DNS error)Hostname of the webhook endpoint is no longer resolvable, e.g. after renaming the ERP environment or an expired DNS recordCheck and restore the DNS record for the endpoint domain; then retrieve missed events via the batch endpointHookSentError (definitive failure)PSB has retried for 5 days without a 2xx responseCheck endpoint logs; monitor the HookSentError topic (see below); retrieve missed events via the batch endpointOld invoices re-deliveredAPI does not return 2xx on first receiptEndpoint must always return 2xx, even for asynchronous processingDuplicate processing on retryNo idempotency checkUse X-EConnect-Delivery header (unique UUID) for deduplication
Monitoring and recovering from HookSentError

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:

  • Set up monitoring: subscribe a hook (e.g. a mail hook) to the HookSentError topic so that a definitive delivery failure is actively signalled rather than silently dropped. Consider also subscribing to HookSentRetry for early detection.
  • Fix the root cause: for a DNS error, check and restore the DNS record for the endpoint domain; for timeouts, make the endpoint respond faster (2xx within 100 sec, offload heavy processing asynchronously).
  • Recover missed events: events that failed during the outage can still be retrieved via the batch endpoint. The 5-day retry window means that timely recovery within that period can still catch up via regular delivery.
Best practices
  • Always use HTTPS for your webhook endpoint
  • Implement idempotent processing: in rare cases the PSB may deliver an event more than once
  • Return a 2xx status code quickly: the PSB treats any non-2xx response as an error and will retry the event
  • Log all received events for debugging and auditing
  • Use a queue on your side if processing takes time; confirm receipt first and process afterwards
Frequently asked questions
How do I verify an incoming webhook with HMAC SHA256?

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.

Why should I check the sentOn field in the payload?

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.

How do I avoid problems with idempotent processing and retries?

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.


Prefer to retrieve documents in bulk? Check out the batch hook.

View the webhook endpoints