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

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.

Adding a topic to an existing hook

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

Commonly used topics
TopicWhenInvoiceReceivedA purchase invoice has been receivedInvoiceReceivedErrorA purchase invoice was rejected by the PSB (technical error)InvoiceSentA sales invoice has been sent successfullyInvoiceSentRetryA retry attempt has startedInvoiceSentErrorNot delivered after all retries (or direct 4xx); no further retries. This is technical non-delivery, not a business rejection by the recipient. A business rejection (e.g. "invoice incorrect") goes via MLS Reject or Invoice Response, not via this topicInvoiceResponseReceivedAn Invoice Response (status message) has been receivedInvoiceResponseSentAn Invoice Response has been successfully sent to the supplierInvoiceResponseSentRetryTemporary error while sending an Invoice Response; a retry followsInvoiceResponseSentErrorAn Invoice Response could not be deliveredMessageLevelStatusReceivedAn 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. 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:

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. If multiple hooks with filters match the same topic: the longest filter (by character count) wins -- for example, a filter with an extra && clause wins over a shorter filter without that clause. This prevents duplicate delivery without requiring filters to be mutually exclusive.
  4. 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.)*sent / *SentAll topics ending in Sent: InvoiceSent, InvoiceResponseSent, OrderSent, MessageLevelStatusSent, HookSent*ReceivedAll receive events (InvoiceReceived, OrderReceived, InvoiceResponseReceived, etc.)Invoice*All invoice-related events

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.

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 endpointEndpoint offline / repeated HTTP 404 (acceptance or production)Customer endpoint temporarily switched off (development, maintenance) or genuinely unreachableFirst check whether the downtime is intentional and temporary (see below); this is not an eConnect platform incidentOld 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 deduplicationInvoice received but does not arrive anywhere; no webhook errors visibleThe PSB has received the purchase invoice (topic InvoiceReceived), but no hook is registered on that topic (never configured, or removed in the meantime)Check the hook configuration first: is 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 activeMany MessageLevelStatusSent e-mails despite few sent e-invoicesMail hook (mailto:) or HTTP webhook listens on the wildcard *sent instead of on InvoiceSentChange the topic configuration to InvoiceSent (same change on the mail hook as on the HTTP webhook); see Wildcard topics
Invoice received but not delivered anywhere (no InvoiceReceived 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.

  • No webhook errors is not proof that the hook works. HookSentError only exists if a hook is configured that fails to deliver.
  • A hook may have been removed in the meantime. Earlier successful delivery to the same party/endpoint proves the hook existed, not that it is still there now. Always check the current hook configuration first instead of treating this as a first-time setup issue.
  • A receipt confirmation at the sender's side is not the same as delivery to the receiving software.

Fix a missing hook by updating the existing hook instead of deleting and recreating it.

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.

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.

  1. Confirm the symptom: 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.
  2. Ask whether the downtime is intentional and temporary (developers/customer).
  3. Confirmed temporary → note it, no further action on eConnect's side until the endpoint is back up. Once the endpoint returns 2xx again, missed events can be retrieved via the batch endpoint.
  4. Not intentional or unknown → follow the standard HookSentError runbook above (DNS/SSL/timeout, logs, monitoring, batch recovery).
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.

What does HookSentError or an endpoint that appears offline on acceptance or production mean?

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.

Why do I get many MessageLevelStatusSent e-mails while I send few invoices?

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