Configurar webhooks en la PSB: topics, seguridad HMAC SHA256 e IP-whitelisting.
Los webhooks son la forma principal de recibir notificaciones en tiempo real de la PSB. Con cada evento relevante (una factura recibida, un cambio de estado, una confirmación de entrega), la PSB envía una solicitud HTTP POST a su endpoint con los detalles del evento.
Usted registra un webhook (un "hook") en la PSB con una URL y un topic. La PSB envía entonces todos los eventos de ese topic a su URL. Cada evento contiene los datos relevantes como payload JSON.
No hay interfaz disponible: actualmente no es posible configurar un hook a través de la interfaz de usuario de la plataforma. Los hooks se configuran a través de la API o del soporte de eConnect. Una interfaz de autoservicio para hooks está en la hoja de ruta (prevista para el T4 2026) y formará parte de Control (Platform 2.0): los usuarios podrán entonces crear, modificar y eliminar hooks por sí mismos. Hasta entonces, no está previsto ningún artículo paso a paso para la configuración manual de hooks a través de la API.
Registre un hook a través de la API:
POST /api/v1/hook
Los principales elementos de configuración:
InvoiceReceived)Nota: evite eliminar y recrear hooks rápidamente para el mismo partyId y topic. Debido a condiciones de carrera en el procesamiento de tareas, esto puede resultar temporalmente en ningún hook activo, lo que causa que los eventos no se entreguen. Espere brevemente después de eliminar un hook antes de crear uno nuevo, o utilice una actualización en lugar de eliminar + crear.
¿Desea agregar un topic adicional (por ejemplo, InvoiceReceived) a un hook existente? Actualice la configuración webhook existente a través de la API /hook habitual para que el topic deseado esté incluido en el array topics:
{
"topics": ["InvoiceReceived"]
}
Actualice el hook existente en lugar de eliminarlo y volver a crearlo. Eliminar y volver a crear introduce una race condition donde los eventos pueden no entregarse temporalmente (véase la advertencia anterior).
No es autoservicio para clientes no técnicos. Agregar un topic a un hook existente es una operación técnica: la persona que la realiza debe saber dónde debe apuntar el webhook (la URL del endpoint) y comprender qué es un webhook. Un cliente no técnico generalmente no puede hacerlo de forma independiente. Involve a someone with technical knowledge (IT del cliente o soporte de eConnect).
InvoiceReceivedInvoiceSentInvoiceSentErrorInvoiceSentRetryInvoiceResponseReceivedMessageLevelStatusReceivedMessageLevelStatusSentOrderReceivedMLS (Message Level Status) es el sucesor del antiguo MLR y proporciona a la parte remitente información sobre la recepción y el procesamiento de un documento. MLS no está habilitado por defecto y debe configurarse por party a través de la capability reviews en la configuración SMP. Una vez habilitado, la PSB gestiona el MLS de forma automática: como Service Provider receptor, la PSB envía un mensaje MLS de vuelta al remitente tras la recepción y entrega.
Cuando usted envía documentos a través de la PSB, recibe la respuesta MLS de la parte receptora como evento webhook en el topic MessageLevelStatusReceived. Como Service Provider receptor, sigue un webhook independiente con el topic MessageLevelStatusSent en cuanto la PSB envía en su nombre un MLS al SP remitente. Utilice este topic para supervisar si sus propias respuestas MLS emitidas se entregan correctamente.
MessageLevelStatusSent no es una confirmación de una factura enviada. Este topic se refiere a un MLS que la PSB envía como respuesta a un documento recibido (usted, como SP receptor, devuelve la notificación de estado al remitente original), no a una factura electrónica que usted mismo haya enviado. Ante muchas notificaciones MessageLevelStatusSent junto con pocas facturas enviadas: compruebe si el hook escucha el wildcard *sent en lugar de específicamente InvoiceSent (véase Topics wildcard).
El payload contiene entre otros:
documentIdrefToDocumentIddetails.statusCodeAP (accepted), RE (rejected), AB (acknowledged)details.descriptionPara recibir mensajes MLS, el hook de Peppol debe contener el campo mlsType. Los valores posibles son ALWAYS_SEND (siempre enviar MLS de vuelta) y FAILURE_ONLY (solo en caso de rechazo).
Consejo: puede recuperar el documento MLS completo a través de
GET /api/v1-beta/{partyId}/generic/{documentId}/download, pero el payload del webhook generalmente contiene información suficiente.
El payload contiene siempre al menos los siguientes campos:
topicInvoiceReceived o InvoiceSentErrordocumentIdrefToDocumentIdMessageLevelStatusSent/MessageLevelStatusReceived, documentId es el mensaje MLS y refToDocumentId la factura o el pedido originalpartyIdlegalEntityId)createdBypeppol, email, network, manualUpload, ksef u otra etiqueta de canal. Use este campo para enrutar el procesamiento posterior por origensentOnPueden aparecer campos de primer nivel nuevos o desconocidos y extensiones dentro de details (payloads forward-compatible). Construya el parser para ignorar los campos desconocidos en lugar de devolver un error.
Sin importes de factura en el payload. El payload del webhook no contiene a propósito importes (total, IVA, importes de línea) ni líneas de factura. Los importes se obtienen del documento XML a través de los endpoints de descarga. Es una decisión de privacy-by-design: el tráfico de notificación no debe contener contenido financiero.
Deduplique los documentos entrantes por Invoice/ID + partyId del emisor + issueDate del XML recuperado. Es responsabilidad de la aplicación receptora, no de la PSB. La PSB garantiza la entrega at-least-once de la notificación, pero no vigila duplicados de contenido en otro canal de facturación electrónica.
Cada payload se compone de los campos generales anteriores más un bloque details específico del topic. Los ejemplos proceden de la documentación DevOps de la PSB y sirven de referencia al construir o probar un consumidor 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 e InvoiceSentError siguen la misma estructura. En Retry, attempt aumenta; en Error, el campo message describe la causa final.
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"
}
Interprete details.statusCode (AP accepted, RE rejected o AB acknowledged), details.description (explicación de la parte receptora) y refToDocumentId (referencia al documento original).
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 y el resto de topics de pedido usan estructuras comparables con un bloque details específico. Construya un parser que ignore campos desconocidos: el elemento details es dinámico y puede incluir campos nuevos.
En v1-beta un hook puede contener un array output con reglas que determinan qué topic se publica según el resultado de la acción. Así se configura un enrutado dinámico sin que el código llamante conozca la lógica de respaldo.
Cada regla de salida tiene dos campos:
when400, 404 o 500topic* (como en Send*Fallback) se expande al topic originalEl primer when que coincide determina el topic resultante. Si ninguna regla coincide, o falta el campo output, el hook vuelve a los topics predeterminados.
Un escenario habitual es Peppol con respaldo por correo: un hook Peppol publica un topic Send*Fallback ante errores de entrega (HTTP 400, 404 o 500), y un hook de correo independiente lo escucha y envía la factura por correo.
{
"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
}
Si el mismo hook de correo también escucha explícitamente Send* (además de Send*Fallback), aparece automáticamente como canal peppol-fallback en la búsqueda multicanal queryRecipientParty. El código llamante puede elegir ese canal con ?channel=peppol-fallback en los endpoints de envío.
Los topics de salida condicional no se limitan al respaldo por correo. El mecanismo vale para cualquier combinación de topics y también puede enrutar códigos de error concretos a un hook SFTP o a un webhook externo.
La PSB protege todas las entregas de webhooks con firmas HMAC SHA256. En cada solicitud, la PSB envía el header:
X-EConnect-Signature: sha256={handtekening}
Para verificar la firma:
X-EConnect-Signature.Compruebe también el campo sentOn en el payload. Si esta marca temporal tiene más de 5 minutos de antigüedad, rechace la solicitud. Esto previene replay attacks en las que una solicitud interceptada se reproduce posteriormente.
Además de la verificación HMAC, la PSB ofrece capas de seguridad adicionales:
51.144.92.226 y 13.81.124.198. Producción: 104.40.188.59 y 104.47.148.207Si tiene múltiples hooks configurados, la PSB determina qué hook utilizar según:
&& adicional gana a un filtro más corto sin esa cláusula. Esto evita la entrega duplicada sin obligar a que los filtros sean mutuamente excluyentes.1 < 02 < 10 < dico < peppol)Además de los topics específicos, también puede usar patrones wildcard para capturar varios tipos de eventos con un solo hook.
Send*Send*sent / *SentSent: InvoiceSent, InvoiceResponseSent, OrderSent, MessageLevelStatusSent, HookSent*ReceivedInvoiceReceived, OrderReceived, InvoiceResponseReceived, etc.)Invoice*Send* no es lo mismo que *sent. Send* coincide con topics que comienzan por Send; ese patrón no aparece entre los topics estándar de la PSB. Para capturar todos los eventos "sent" (InvoiceSent, MessageLevelStatusSent, HookSent, etc.), use *sent. InvoiceSentRetry e InvoiceSentError no terminan en Sent y por tanto no quedan cubiertos por el sufijo *sent.
Las etiquetas de estado, por ejemplo en un daily digest o en un pipeline de eventos, significan otra cosa que un reintento fallido de webhook (HookSentError):
En resumen: hay drop hook, luego dropped (ignorado a propósito, sin dead letter); no hay hooks coincidentes, luego dead letter.
A continuación se indican los problemas más comunes con los webhooks y cómo resolverlos.
sha256=sentOn tiene más de 5 minutos de antigüedadHookSentError (véase más abajo); recuperar eventos perdidos a través del endpoint de lotesX-EConnect-Delivery (UUID único) para deduplicaciónInvoiceReceived), pero no hay ningún hook registrado en ese topic (nunca configurado, o eliminado mientras tanto)InvoiceReceived en los topics? Sin un hook en ese topic no aparece HookSentError, por lo que la ausencia de errores no prueba que el hook esté activoMessageLevelStatusSent con pocas facturas enviadasmailto:) o webhook HTTP escucha el wildcard *sent en lugar de InvoiceSentInvoiceSent (mismo cambio en el mail-hook y en el webhook HTTP); véase Topics wildcardInvoiceReceived)El PSB puede recibir una factura de compra sin reenviarla a la parte receptora. El topic InvoiceReceived significa que el PSB ha recibido la factura; sin un hook en ese topic, no se envía nada a un endpoint.
HookSentError solo existe si hay un hook configurado que falla al entregar.Corrija un hook faltante actualizando el hook existente en lugar de eliminarlo y volver a crearlo.
El PSB envía eventos webhook (p. ej. InvoiceReceived al recibir una factura electrónica) al endpoint configurado. Si ese endpoint es inaccesible — por un tiempo de espera, un error SSL o un error DNS como No such host is known — el PSB reintenta con retroceso exponencial hasta 12 veces, repartidas en unos 6 días (unas 137 horas, tiempo de espera 100 seg. por intento). Cada intento genera un evento HookSentRetry; tras esos intentos sin respuesta 2xx, se emite HookSentError como estado final y el PSB deja de reintentar.
Consecuencia: una factura electrónica recibida no se entregará al cliente mientras el endpoint sea inaccesible, sin que el cliente lo note directamente. Acciones recomendadas:
HookSentError para que un fallo de entrega definitivo se señalice activamente en lugar de pasar desapercibido. Considerar también HookSentRetry para detección temprana.Primera línea: interrupción del lado del cliente vs. problema técnico (aceptación y producción)
HookSentError, un HTTP 404 repetido o "endpoint fuera de servicio"/"entorno de aceptación apagado" también puede deberse a que el cliente apaga temporalmente el endpoint de aceptación o producción (desarrollo, mantenimiento). Esto no es una incidencia de la plataforma eConnect.
HookSentError, endpoint fuera de servicio o HTTP 404 en el endpoint webhook configurado por el cliente. A menudo visible como el texto exacto Response status code does not indicate success: 404 (Not Found) (normalmente una HttpRequestException de .NET en la solicitud saliente del hook). Este es un 404 del propio endpoint del cliente (apagado o ruta incorrecta), no el error Peppol PartyId not found al enviar (404 de SMP), ni un 404 de recurso REST del manejo de errores de la API.HookSentError más abajo (DNS/SSL/timeout, logs, monitorización, recuperación batch).Tome el cuerpo JSON sin procesar de la solicitud, calcule el hash HMAC SHA256 con el secreto que indicó al crear el hook y compárelo con el valor del header X-EConnect-Signature (después del prefijo sha256=). Si coinciden, sabe que la solicitud proviene de la PSB y no fue modificada en tránsito.
Verifique que sentOn no tenga más de 5 minutos de antigüedad. Si la marca de tiempo es demasiado antigua, rechace la solicitud. Esto previene ataques de repetición, donde una solicitud interceptada previamente se reenvía más tarde, incluso si la firma es técnicamente válida.
Implemente un procesamiento idempotente en su lado, ya que la PSB puede entregar un evento más de una vez en casos excepcionales. Además, devuelva rápidamente un código de estado HTTP 2xx: cualquier otra respuesta se considera un error y desencadena un reintento. Para un procesamiento pesado, confirme primero la recepción y luego procese a través de una cola.
HookSentError aparece después de que el PSB haya intentado 12 veces (unos 6 días, unas 137 horas) sin respuesta 2xx. Un HTTP 404 repetido o el mensaje de que el endpoint está fuera de servicio suele deberse a que el cliente apaga temporalmente el endpoint de aceptación o producción (desarrollo, mantenimiento); esto no es una incidencia de la plataforma eConnect. Confirme primero si la interrupción es intencionada y temporal; si es así, no se necesita ninguna acción adicional hasta que el endpoint vuelva a ser accesible. Los eventos perdidos pueden recuperarse a través del endpoint de lotes dentro de la ventana de reintento de unos 6 días.
Este es el comportamiento esperado si el mail-hook (mailto:) o el webhook HTTP escucha el wildcard *sent en lugar de específicamente InvoiceSent. MessageLevelStatusSent no es una confirmación de una factura enviada por usted mismo, sino de un MLS que la PSB envía como respuesta a un documento recibido, y queda bajo el patrón *sent junto con InvoiceSent, OrderSent y HookSent. Si solo desea notificaciones al enviar facturas, cambie la configuración del topic a InvoiceSent. InvoiceSentRetry e InvoiceSentError no terminan en Sent y por tanto no quedan cubiertos por *sent.
¿Prefiere recuperar los documentos en masa? Consulte el batch hook.
Ver los endpoints webhook