Configurer les webhooks dans la PSB : topics, sécurisation HMAC SHA256 et IP-whitelisting.
Les webhooks sont le moyen principal de recevoir des notifications en temps réel de la PSB. Lors de chaque événement pertinent (une facture reçue, un changement de statut, une confirmation de livraison), la PSB envoie une requête HTTP POST vers votre endpoint avec les détails de l'événement.
Vous enregistrez un webhook (un "hook") dans la PSB avec une URL et un topic. La PSB envoie ensuite tous les événements de ce topic vers votre URL. Chaque événement contient les données pertinentes sous forme de payload JSON.
Pas d'interface disponible : il n'est actuellement pas possible de configurer un hook via l'interface utilisateur de la plateforme. Les hooks sont configurés via l'API ou via le support eConnect. Une interface self-service pour les hooks est prévue dans la feuille de route (planifiée pour le T4 2026) et fera partie de Control (Platform 2.0) : les utilisateurs pourront alors créer, modifier et supprimer des hooks eux-mêmes. D'ici là, aucun article pas-à-pas pour la configuration manuelle des hooks via l'API n'est prévu.
Enregistrez un hook via l'API :
POST /api/v1/hook
Les principaux éléments de configuration :
InvoiceReceived)Attention : évitez de supprimer et recréer rapidement des hooks pour le même partyId et le même topic. En raison de race conditions dans le traitement des tâches, il peut arriver que temporairement aucun hook actif n'existe, ce qui empêche la livraison des événements. Attendez un instant après la suppression d'un hook avant d'en créer un nouveau, ou utilisez une mise à jour au lieu de supprimer puis recréer.
Vous souhaitez ajouter un topic supplémentaire (par ex. InvoiceReceived) à un hook existant ? Mettez à jour la configuration webhook existante via l'API /hook habituelle afin que le topic souhaité figure dans le tableau topics :
{
"topics": ["InvoiceReceived"]
}
Mettez à jour le hook existant plutôt que de le supprimer et de le recréer. Supprimer et recréer introduit une race condition où les événements peuvent ne pas être livrés temporairement (voir l'avertissement ci-dessus).
Pas en libre-service pour les clients non techniques. L'ajout d'un topic à un hook existant est une opération technique : la personne qui l'effectue doit savoir où le webhook doit pointer (l'URL de l'endpoint) et comprendre ce qu'est un webhook. Un client non technique ne peut généralement pas le faire de manière autonome. Faites appel à quelqu'un ayant des connaissances techniques (l'IT du client ou le support eConnect).
InvoiceReceivedInvoiceSentInvoiceSentErrorInvoiceSentRetryInvoiceResponseReceivedMessageLevelStatusReceivedMessageLevelStatusSentOrderReceivedLe MLS (Message Level Status) est le successeur de l'ancien MLR et fournit à la partie expéditrice un retour sur la réception et le traitement d'un document. Le MLS n'est pas activé par défaut et doit être configuré par party via la capability reviews dans la configuration SMP. Une fois le MLS activé, la PSB le gère automatiquement : en tant que Service Provider récepteur, la PSB renvoie un message MLS à l'expéditeur après réception et livraison.
Lorsque vous envoyez vous-même des documents via la PSB, vous recevez le retour MLS de la partie réceptrice sous forme d'événement webhook sur le topic MessageLevelStatusReceived. En tant que Service Provider récepteur, un webhook distinct avec le topic MessageLevelStatusSent suit dès que la PSB envoie en votre nom un MLS au SP expéditeur. Utilisez ce topic pour vérifier que vos propres réponses MLS sortantes sont bien livrées.
MessageLevelStatusSent n'est pas une confirmation de facture envoyée. Ce topic concerne un MLS que la PSB envoie en réponse à un document reçu (vous, en tant que SP récepteur, renvoyez la notification de statut à l'expéditeur d'origine), pas une facture électronique que vous avez envoyée vous-même. En cas de nombreuses notifications MessageLevelStatusSent avec peu de factures envoyées : vérifiez si le hook écoute le wildcard *sent au lieu de spécifiquement InvoiceSent (voir Topics wildcard).
Le payload contient entre autres :
documentIdrefToDocumentIddetails.statusCodeAP (accepted), RE (rejected), AB (acknowledged)details.descriptionPour recevoir des messages MLS, le hook Peppol doit contenir le champ mlsType. Les valeurs possibles sont ALWAYS_SEND (toujours renvoyer un MLS) et FAILURE_ONLY (uniquement en cas de rejet).
Conseil : vous pouvez récupérer le document MLS complet via
GET /api/v1-beta/{partyId}/generic/{documentId}/download, mais le payload du webhook contient généralement suffisamment d'informations.
Le payload contient toujours au moins les champs suivants :
topicInvoiceReceived ou InvoiceSentErrordocumentIdrefToDocumentIdMessageLevelStatusSent/MessageLevelStatusReceived, documentId est le message MLS lui-même et refToDocumentId la facture ou la commande d'originepartyIdlegalEntityId)createdBypeppol, email, network, manualUpload, ksef ou une autre indication de canal. Utilisez ce champ pour router le traitement aval par sourcesentOnDe nouveaux champs de premier niveau inconnus et des extensions dans details peuvent apparaître (payloads forward-compatible). Construisez le parser pour ignorer les champs inconnus au lieu de lever une erreur.
Pas de montants de facture dans le payload. Le payload du webhook ne contient volontairement aucun montant (total, TVA, montants de ligne) ni lignes de facture. Les montants se récupèrent dans le document XML via les endpoints de téléchargement. C'est un choix privacy-by-design : le trafic de notification ne doit pas contenir de contenu financier.
Dédupliquez les documents entrants sur Invoice/ID + partyId de l'expéditeur + issueDate du fichier XML récupéré. C'est la responsabilité de l'application réceptrice, pas de la PSB. La PSB garantit une livraison at-least-once de la notification elle-même, mais ne surveille pas les doublons de contenu dans un autre canal de facturation électronique.
Chaque payload se compose des champs généraux ci-dessus, complétés d'un bloc details spécifique au topic. Les exemples suivants proviennent de la documentation DevOps de la PSB et servent de référence pour construire ou tester un consumer de webhook.
InvoiceReceived{
"topic": "InvoiceReceived",
"partyId": "NL:KVK:12345678",
"hookId": "1",
"documentId": "5618b40a-822d-4e74-8894-b489617dbaa4",
"message": "Invoice successfully received.",
"details": {
"id": "invoiceId",
"sender": "0106:87654321",
"createdBy": "peppol",
"documentTypeId": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1"
},
"createdOn": "2026-04-30T09:00:00.0000000+00:00",
"sentOn": "2026-04-30T09:00:00.5000000+00:00"
}
InvoiceSent{
"topic": "InvoiceSent",
"partyId": "NL:KVK:12345678",
"hookId": "1",
"documentId": "5618b40a-822d-4e74-8894-b489617dbaa4",
"message": "Invoice successfully sent.",
"details": {
"id": "invoiceId",
"recipient": "0106:87654321",
"endpoint": "https://accp-ap.econnect.eu/as4/v1",
"protocol": "As4",
"attempt": "1",
"messageId": "3e3b336c-6563-431c-93bc-6b109dedfcc5@econnect.eu",
"sourceDocumentTypeId": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1",
"targetDocumentTypeId": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1"
},
"createdOn": "2026-04-30T09:31:57.7444125+00:00",
"sentOn": "2026-04-30T09:31:57.8703556+00:00"
}
InvoiceSentRetry et InvoiceSentError suivent la même structure. Pour Retry, attempt est incrémenté ; pour Error, le champ message décrit la cause finale.
MessageLevelStatusReceived{
"topic": "MessageLevelStatusReceived",
"partyId": "NL:KVK:12345678",
"hookId": "mls",
"documentId": "615a914f-4a4d-4b3a-a258-828efb2817a6",
"refToDocumentId": "d5e3bc34-96c8-4daf-b260-b7b182fa4760",
"message": "'MessageLevelStatus' received. Status: 'Accept' Description: 'Document is accepted'.",
"details": {
"statusCode": "AP",
"status": "Accept",
"description": "Document is accepted",
"documentTypeId": "urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:peppol:edec:mls:1.0::2.1",
"profileId": "urn:peppol:edec:mls"
},
"createdOn": "2026-03-11T22:43:18.2766651+00:00",
"sentOn": "2026-03-11T22:43:18.8225397+00:00"
}
Interprétez details.statusCode (AP accepted, RE rejected ou AB acknowledged), details.description (explication de la partie réceptrice) et refToDocumentId (référence au document d'origine).
MessageLevelStatusSent{
"topic": "MessageLevelStatusSent",
"partyId": "NL:KVK:ECONNECTTS",
"hookId": "mls",
"documentId": "c4a31897-66ac-468c-a7e8-14a24f9af114",
"refToDocumentId": "be7005ff-5555-41eb-afef-f5ffec2b29ed",
"message": "'ApplicationResponse' successfully sent.",
"details": {
"statusCode": "AP",
"status": "Accept",
"description": "Document is accepted",
"profileId": "urn:peppol:edec:mls",
"sourceDocumentTypeId": "urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:peppol:edec:mls:1.0::2.1",
"attempt": "1",
"protocol": "As4"
},
"createdOn": "2026-03-12T21:12:20.1621727+00:00",
"sentOn": "2026-03-12T21:12:20.3726218+00:00"
}
OrderSent et les autres topics de commande utilisent des structures comparables avec un bloc details spécifique. Construisez un parser qui ignore les champs inconnus : l'élément details est dynamique et peut contenir de nouveaux champs.
En v1-beta, un hook peut contenir un tableau output avec des règles qui déterminent quel topic est publié selon le résultat de l'action. Cela permet un routage dynamique sans que le code appelant connaisse lui-même la logique de repli.
Chaque règle de sortie comporte deux champs :
when400, 404 ou 500topic* (comme dans Send*Fallback) est développé vers le topic d'origineLe premier when correspondant détermine le topic résultant. Si aucune règle ne correspond, ou si le champ output est absent, le hook retombe sur les topics par défaut.
Un scénario courant est Peppol avec repli e-mail : un hook Peppol publie un topic Send*Fallback en cas d'erreur de livraison (HTTP 400, 404 ou 500), qu'un hook mail distinct écoute pour envoyer la facture par e-mail.
{
"id": "peppol",
"action": "peppol",
"name": "peppol hook",
"topics": ["Send*"],
"output": [
{ "when": "400", "topic": "Send*Fallback" },
{ "when": "404", "topic": "Send*Fallback" },
{ "when": "500", "topic": "Send*Fallback" }
],
"isActive": true
}
Lorsque le même hook mail écoute aussi explicitement Send* (en plus de Send*Fallback), il apparaît automatiquement comme canal peppol-fallback dans la recherche multi-canal queryRecipientParty. Le code appelant peut alors choisir ce canal via ?channel=peppol-fallback sur les endpoints d'envoi.
Les topics de sortie conditionnelle ne se limitent pas au repli e-mail. Le mécanisme fonctionne pour toute combinaison de topics et peut aussi router des codes d'erreur vers un hook SFTP ou un webhook externe.
La PSB sécurise toutes les livraisons de webhooks avec des signatures HMAC SHA256. À chaque requête, la PSB envoie le header :
X-EConnect-Signature: sha256={handtekening}
Pour vérifier la signature :
X-EConnect-Signature.Vérifiez également le champ sentOn dans le payload. Si ce timestamp est antérieur de plus de 5 minutes, rejetez la requête. Cela prévient les replay attacks où une requête interceptée est rejouée ultérieurement.
En plus de la vérification HMAC, la PSB propose des couches de sécurité supplémentaires :
51.144.92.226 et 13.81.124.198. Production : 104.40.188.59 et 104.47.148.207Si vous avez configuré plusieurs hooks, la PSB détermine quel hook utiliser selon :
&& supplémentaire gagne sur un filtre plus court sans cette clause. Cela évite une double livraison sans obliger les filtres à s'exclure mutuellement.1 < 02 < 10 < dico < peppol)En plus des topics spécifiques, vous pouvez utiliser des motifs wildcard pour capturer plusieurs types d'événements avec un seul hook.
Send*Send*sent / *SentSent : InvoiceSent, InvoiceResponseSent, OrderSent, MessageLevelStatusSent, HookSent*ReceivedInvoiceReceived, OrderReceived, InvoiceResponseReceived, etc.)Invoice*Send* n'est pas identique à *sent. Send* correspond aux topics commençant par Send ; ce motif n'existe pas parmi les topics PSB standards. Pour capturer tous les événements « sent » (InvoiceSent, MessageLevelStatusSent, HookSent, etc.), utilisez *sent. InvoiceSentRetry et InvoiceSentError ne se terminent pas par Sent et ne sont donc pas couverts par le suffixe *sent.
Les libellés de statut, par exemple dans un daily digest ou un pipeline d'événements, signifient autre chose qu'un échec de retry webhook (HookSentError) :
En bref : drop hook présent, donc dropped (ignoré volontairement, pas de dead letter) ; aucun hook correspondant, donc dead letter.
Voici les problèmes courants avec les webhooks et comment les résoudre.
sha256=sentOn est plus ancien que 5 minutesHookSentError (voir ci-dessous) ; récupérer les événements manqués via le endpoint batchX-EConnect-Delivery (UUID unique) pour la déduplicationInvoiceReceived), mais aucun hook n'est enregistré sur ce topic (jamais configuré, ou supprimé entre-temps)InvoiceReceived figure-t-il dans les topics ? Sans hook sur ce topic, aucun HookSentError n'apparaît, donc l'absence de message d'erreur ne prouve pas que le hook est actifMessageLevelStatusSent malgré peu de factures envoyéesmailto:) ou webhook HTTP écoute le wildcard *sent au lieu de InvoiceSentInvoiceSent (même modification sur le mail-hook et sur le webhook HTTP) ; voir Topics wildcardInvoiceReceived)Le PSB peut recevoir une facture d'achat sans la transmettre à la partie destinataire. Le topic InvoiceReceived signifie que le PSB a reçu la facture ; sans hook sur ce topic, rien n'est envoyé à un endpoint.
HookSentError n'existe que si un hook est configuré et échoue à livrer.Corrigez un hook manquant en mettant à jour le hook existant plutôt qu'en le supprimant et le recréant.
Le PSB envoie des événements webhook (p. ex. InvoiceReceived lors de la réception d'une facture électronique) à l'endpoint configuré. Si cet endpoint est inaccessible - en raison d'un délai d'attente, d'une erreur SSL ou d'une erreur DNS comme No such host is known - le PSB réessaie avec un recul exponentiel jusqu'à 12 fois, réparties sur environ 6 jours (environ 137 heures, délai 100 sec. par tentative). Chaque tentative génère un événement HookSentRetry ; après ces tentatives sans réponse 2xx, HookSentError est émis comme statut final et le PSB cesse de réessayer.
Conséquence : une facture électronique reçue ne sera pas livrée au client tant que l'endpoint est inaccessible, sans que le client s'en aperçoive directement. Actions recommandées :
HookSentError afin qu'un échec de livraison définitif soit signalé activement plutôt que de passer inaperçu. Envisager également HookSentRetry pour une détection précoce.Première ligne : interruption côté client vs. problème technique (acceptation et production)
HookSentError, un HTTP 404 répété ou « endpoint hors ligne »/« environnement d'acceptation désactivé » peut aussi provenir du fait que le client désactive temporairement l'endpoint d'acceptation ou de production (développement, maintenance). Ce n'est pas un incident de la plateforme eConnect.
HookSentError, endpoint hors ligne ou HTTP 404 sur l'endpoint webhook configuré par le client. Souvent visible sous la forme exacte Response status code does not indicate success: 404 (Not Found) (généralement une HttpRequestException .NET lors de la requête hook sortante). Il s'agit d'un 404 provenant de l'endpoint du client lui-même (hors ligne ou chemin incorrect), pas de l'erreur Peppol PartyId not found lors de l'envoi (404 SMP), ni d'un 404 de ressource REST issu de la gestion des erreurs de l'API.HookSentError standard ci-dessous (DNS/SSL/timeout, journaux, monitoring, récupération batch).Prenez le corps JSON brut de la requête, calculez le hash HMAC SHA256 avec le secret que vous avez indiqué lors de la création du hook et comparez-le avec la valeur dans le header X-EConnect-Signature (après le préfixe sha256=). Si les valeurs correspondent, vous savez que la requête provient de la PSB et n'a pas été modifiée en transit.
Vérifiez que sentOn n'a pas plus de 5 minutes. Si l'horodatage est trop ancien, refusez la requête. Cela empêche les attaques par rejeu, où une requête interceptée est soumise à nouveau ultérieurement, même si la signature est techniquement valide.
Implémentez un traitement idempotent de votre côté, car la PSB peut dans de rares cas livrer un événement plusieurs fois. Retournez également rapidement un statut HTTP 2xx : toute autre réponse est considérée comme une erreur et déclenche un nouvel essai. Pour un traitement lourd, confirmez d'abord la réception, puis traitez via une file d'attente.
HookSentError survient après que le PSB a tenté 12 fois (environ 6 jours, environ 137 heures) sans réponse 2xx. Un HTTP 404 répété ou le message indiquant que l'endpoint est hors ligne provient souvent du fait que le client désactive temporairement l'endpoint d'acceptation ou de production (développement, maintenance) — ce n'est pas un incident de la plateforme eConnect. Confirmer d'abord si l'interruption est intentionnelle et temporaire ; si oui, aucune action supplémentaire n'est nécessaire jusqu'à ce que l'endpoint soit de nouveau accessible. Les événements manqués peuvent toujours être récupérés via le endpoint batch dans la fenêtre d'environ 6 jours.
C'est un comportement attendu si le mail-hook (mailto:) ou le webhook HTTP écoute le wildcard *sent au lieu de spécifiquement InvoiceSent. MessageLevelStatusSent n'est pas une confirmation d'une facture envoyée par vous-même, mais d'un MLS que la PSB envoie en réponse à un document reçu, et il relève du motif *sent avec InvoiceSent, OrderSent et HookSent. Si vous ne voulez des notifications qu'à l'envoi de factures, modifiez la configuration du topic vers InvoiceSent. InvoiceSentRetry et InvoiceSentError ne se terminent pas par Sent et ne sont donc pas couverts par *sent.
Vous préférez récupérer les documents en masse ? Consultez le batch hook.
Voir les endpoints webhook