Poštár

API Documentation

Webhooks

Use webhooks to receive near real-time updates about incoming documents and lifecycle changes. A webhook payload is signed with HMAC SHA-256 and sent with header X-Webhook-Signature.

Register a webhook

Register webhooks per participant. Include only events you need.

curl -X POST "https://api.pepposh.eu/api/participants/{participantId}/webhooks" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhooks/peppol",
    "events": ["document.received", "mlr", "document.failed"]
  }'
await fetch("https://api.pepposh.eu/api/participants/{participantId}/webhooks", {
  method: "POST",
  headers: {
    Authorization: "Bearer <token>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://your-app.example.com/webhooks/peppol",
    events: ["document.received", "mlr"],
  }),
});

Filtering by document type

Use the optional documentTypes field to limit document event delivery to specific PEPPOL document type identifiers. When omitted or empty, all document types are delivered. This filter has no effect on account.verified and certificate.expiring events.

{
  "url": "https://your-app.example.com/webhooks/peppol",
  "events": ["document.received"],
  "documentTypes": ["invoice", "creditnote"]
}

Webhook event types

EventMeaningTypical action
document.receivedA document was received by your participant.Sync inbox and enqueue internal processing.
mlrMessage Level Response for the same document/message pair: accepted or rejected.Update document status in your ERP. If rejected, review and resend.
invoice.responseInvoice Message Response (IMR, BIS 63) received for an invoice or credit note you sent. A business-level response from the buyer – e.g. AP (Accepted), RE (Rejected), UQ (Under Query), PD (Paid). This is distinct from MLR: MLR is technical, IMR is commercial.Update invoice status in your AR system. On RE, raise dispute flow. On PD, mark as paid.
order.responseOrder Response (BIS 28) received for an order you sent. AP = accepted, RE = rejected.Advance the order workflow – trigger fulfilment on AP, raise review on RE.
document.sentOutbound document was accepted for sending pipeline.Mark as submitted / queued.
document.deliveredOutbound delivery completed successfully.Mark as delivered in ERP or customer UI.
document.failedOutbound sending failed.Raise alert and start retry/review flow on your side.
account.verifiedParticipant/account verification completed.Unlock onboarding steps and enable production actions.
certificate.expiringA certificate is approaching expiry.Rotate certificate before deadline.

Real-life message samples

Webhook envelope is always { id, event, documentType, timestamp, data }. Below are practical examples for each event.

Two webhooks per response message. When an MLR, IMR, or OrderResponse arrives, two webhooks fire: (1) the message-received event (document.received, invoice.response, or order.response); (2) an mlr event for the original outbound document whose status changed. Both payloads include responseDocumentType ("mlr", "imr", or "order-response") and responseDocumentId (the IMR/OR document ID, or null for MLR).

Inbound invoice received — single webhook

document.received

{
  "id": "evt_2Bf0i7k3Qh",
  "event": "document.received",
  "documentType": "invoice",
  "timestamp": "2026-05-06T08:20:11.184Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "tenantExtId": "tenant_ext_001",
    "tenantName": "Acme Holding",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "participantExtId": "participant_ext_001",
    "participantName": "Acme Trading CZ",
    "webhookId": "webhook_123456",
    "webhookExtId": "wh_ext_primary",
    "documentId": "f9af7d6c-ccf5-4f6f-95d4-d5819b95fa8f",
    "documentExtId": "doc_ext_2026_0001",
    "senderName": "Supplier s.r.o.",
    "documentNumber": "INV-2026-0001",
    "recipientName": "Acme Trading CZ",
    "messageId": "ad3f58d2-0175-4d83-a9f9-8e2353baf9e8",
    "sender": { "peppolId": "9929:cz12345678" },
    "receiver": { "peppolId": "9929:cz87654321" },
    "documentType": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##...",
    "receivedAt": "2026-05-06T08:20:10.931Z",
    "status": "delivered",
    "senderRejected": false
  }
}

MLR received (technical ACK/NACK) — two webhooks

1/2 — MLR message arrived · document.received

{
  "id": "evt_4Kp9nB2eRv",
  "event": "document.received",
  "documentType": "mlr",
  "timestamp": "2026-05-06T09:02:38.012Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "tenantExtId": "tenant_ext_001",
    "tenantName": "Acme Holding",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "participantExtId": "participant_ext_001",
    "participantName": "Acme Trading CZ",
    "webhookId": "webhook_123456",
    "webhookExtId": "wh_ext_primary",
    "documentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "documentExtId": null,
    "senderName": null,
    "documentNumber": null,
    "recipientName": "Acme Trading CZ",
    "messageId": "c4d5e6f7-0011-2233-4455-667788990011",
    "sender": { "peppolId": "9929:cz87654321" },
    "receiver": { "peppolId": "9929:cz12345678" },
    "documentType": "urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:poacc:trns:mlr:3::2.1",
    "receivedAt": "2026-05-06T09:02:37.891Z",
    "status": "delivered",
    "senderRejected": false,
    "responseDocumentType": "mlr",
    "responseDocumentId": null
  }
}

2/2 — original document confirmed/rejected · mlr

{
  "id": "evt_3Yu4m8Nq1a",
  "event": "mlr",
  "documentType": "mlr",
  "timestamp": "2026-05-06T09:02:41.443Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "tenantExtId": "tenant_ext_001",
    "tenantName": "Acme Holding",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "participantExtId": "participant_ext_001",
    "participantName": "Acme Trading CZ",
    "webhookId": "webhook_123456",
    "webhookExtId": "wh_ext_primary",
    "documentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "documentExtId": null,
    "senderName": null,
    "documentNumber": null,
    "recipientName": "Acme Trading CZ",
    "messageId": "c4d5e6f7-0011-2233-4455-667788990011",
    "sender": { "peppolId": "9929:cz87654321" },
    "receiver": { "peppolId": "9929:cz12345678" },
    "documentType": "urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:poacc:trns:mlr:3::2.1",
    "receivedAt": "2026-05-06T09:02:37.891Z",
    "status": "confirmed",
    "senderRejected": false,
    "responseDocumentType": "mlr",
    "responseDocumentId": null,
    "responseCode": "AP",
    "accepted": true
  }
}

IMR received (buyer's business response to your invoice) — two webhooks

1/2 — IMR document received · invoice.response

{
  "id": "evt_R9xv3Aq4Lp",
  "event": "invoice.response",
  "documentType": "imr",
  "timestamp": "2026-05-06T11:14:32.771Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "tenantExtId": "tenant_ext_001",
    "tenantName": "Acme Holding",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "participantExtId": "participant_ext_001",
    "participantName": "Acme Trading CZ",
    "webhookId": "webhook_123456",
    "webhookExtId": "wh_ext_primary",
    "documentId": "c83e1b2d-0a47-4d12-b7e5-9f1ac3b84d52",
    "documentExtId": "doc_ext_2026_0002",
    "senderName": "Buyer GmbH",
    "documentNumber": "IMR-AP-2026-0042",
    "recipientName": "Acme Trading CZ",
    "messageId": "7f2c910b-3348-41e7-bba1-0f23abc89d11",
    "sender": { "peppolId": "9930:DE123456789" },
    "receiver": { "peppolId": "9929:cz87654321" },
    "documentType": "urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:poacc:trns:invoice_response:3::2.1",
    "receivedAt": "2026-05-06T11:14:31.904Z",
    "status": "delivered",
    "senderRejected": false,
    "responseDocumentType": "imr",
    "responseDocumentId": "c83e1b2d-0a47-4d12-b7e5-9f1ac3b84d52",
    "responseCode": "AP",
    "accepted": true,
    "referencedDocumentId": "be5d4af1-9cf7-48f7-a4f9-0f5b7e1fbd81"
  }
}

2/2 — original invoice confirmed/rejected · mlr

{
  "id": "evt_5Lm8pD3fWq",
  "event": "mlr",
  "documentType": "invoice",
  "timestamp": "2026-05-06T11:14:33.102Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "tenantExtId": "tenant_ext_001",
    "tenantName": "Acme Holding",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "participantExtId": "participant_ext_001",
    "participantName": "Acme Trading CZ",
    "webhookId": "webhook_123456",
    "webhookExtId": "wh_ext_primary",
    "documentId": "be5d4af1-9cf7-48f7-a4f9-0f5b7e1fbd81",
    "documentExtId": "doc_ext_invoice_001",
    "senderName": "Acme Trading CZ",
    "documentNumber": "INV-2026-0099",
    "recipientName": "Buyer GmbH",
    "messageId": "7f2c910b-3348-41e7-bba1-0f23abc89d11",
    "sender": { "peppolId": "9929:cz87654321" },
    "receiver": { "peppolId": "9930:DE123456789" },
    "documentType": "urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:poacc:trns:invoice_response:3::2.1",
    "receivedAt": "2026-05-06T11:14:31.904Z",
    "status": "confirmed",
    "senderRejected": false,
    "responseDocumentType": "imr",
    "responseDocumentId": "c83e1b2d-0a47-4d12-b7e5-9f1ac3b84d52",
    "responseCode": "AP",
    "accepted": true
  }
}

OrderResponse received — two webhooks

1/2 — OrderResponse document received · order.response

{
  "id": "evt_W2zk5Bm7Nt",
  "event": "order.response",
  "documentType": "order-response",
  "timestamp": "2026-05-06T11:30:18.442Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "tenantExtId": "tenant_ext_001",
    "tenantName": "Acme Holding",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "participantExtId": "participant_ext_001",
    "participantName": "Acme Trading CZ",
    "webhookId": "webhook_123456",
    "webhookExtId": "wh_ext_primary",
    "documentId": "d91f2e4a-7b88-4c63-af29-3e8dc5a72b14",
    "documentExtId": "doc_ext_2026_0003",
    "senderName": "Supplier s.r.o.",
    "documentNumber": "OR-2026-0005",
    "recipientName": "Acme Trading CZ",
    "messageId": "aa8b451c-6712-4d88-92cb-1e0f7dcb3421",
    "sender": { "peppolId": "9929:cz12345678" },
    "receiver": { "peppolId": "9929:cz87654321" },
    "documentType": "urn:oasis:names:specification:ubl:schema:xsd:OrderResponse-2::OrderResponse##urn:fdc:peppol.eu:poacc:trns:order_response:3::2.1",
    "receivedAt": "2026-05-06T11:30:17.810Z",
    "status": "delivered",
    "senderRejected": false,
    "responseDocumentType": "order-response",
    "responseDocumentId": "d91f2e4a-7b88-4c63-af29-3e8dc5a72b14",
    "responseCode": "AP",
    "accepted": true,
    "referencedDocumentId": "f7a1c3d8-2b54-4e01-99c6-8d7af3b90e22"
  }
}

2/2 — original order confirmed/rejected · mlr

{
  "id": "evt_6Nq7rE4gXs",
  "event": "mlr",
  "documentType": "order",
  "timestamp": "2026-05-06T11:30:18.904Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "tenantExtId": "tenant_ext_001",
    "tenantName": "Acme Holding",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "participantExtId": "participant_ext_001",
    "participantName": "Acme Trading CZ",
    "webhookId": "webhook_123456",
    "webhookExtId": "wh_ext_primary",
    "documentId": "f7a1c3d8-2b54-4e01-99c6-8d7af3b90e22",
    "documentExtId": "doc_ext_order_001",
    "senderName": "Acme Trading CZ",
    "documentNumber": "ORD-2026-0022",
    "recipientName": "Supplier s.r.o.",
    "messageId": "aa8b451c-6712-4d88-92cb-1e0f7dcb3421",
    "sender": { "peppolId": "9929:cz87654321" },
    "receiver": { "peppolId": "9929:cz12345678" },
    "documentType": "urn:oasis:names:specification:ubl:schema:xsd:OrderResponse-2::OrderResponse##urn:fdc:peppol.eu:poacc:trns:order_response:3::2.1",
    "receivedAt": "2026-05-06T11:30:17.810Z",
    "status": "confirmed",
    "senderRejected": false,
    "responseDocumentType": "order-response",
    "responseDocumentId": "d91f2e4a-7b88-4c63-af29-3e8dc5a72b14",
    "responseCode": "AP",
    "accepted": true
  }
}

Outbound document lifecycle

document.sent

{
  "id": "evt_9pn2Q0w8Lx",
  "event": "document.sent",
  "documentType": "invoice",
  "timestamp": "2026-05-06T10:01:16.232Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "documentId": "be5d4af1-9cf7-48f7-a4f9-0f5b7e1fbd81",
    "messageId": "f1f12c53-8fb3-4ee9-a0cf-a8d8a696db91",
    "status": "sent"
  }
}

document.delivered

{
  "id": "evt_M4jP9s31xv",
  "event": "document.delivered",
  "documentType": "invoice",
  "timestamp": "2026-05-06T10:03:47.017Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "documentId": "be5d4af1-9cf7-48f7-a4f9-0f5b7e1fbd81",
    "messageId": "f1f12c53-8fb3-4ee9-a0cf-a8d8a696db91",
    "status": "delivered"
  }
}

document.failed

{
  "id": "evt_C1k4n2a7zQ",
  "event": "document.failed",
  "documentType": "invoice",
  "timestamp": "2026-05-06T10:04:25.559Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "documentId": "be5d4af1-9cf7-48f7-a4f9-0f5b7e1fbd81",
    "messageId": "f1f12c53-8fb3-4ee9-a0cf-a8d8a696db91",
    "status": "failed",
    "error": "SMP endpoint unavailable"
  }
}

Account & certificate events

account.verified

{
  "id": "evt_P7w0zv4J9n",
  "event": "account.verified",
  "documentType": null,
  "timestamp": "2026-05-06T10:10:04.291Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "verifiedAt": "2026-05-06T10:10:03.901Z"
  }
}

certificate.expiring

{
  "id": "evt_T4h8qb2M1d",
  "event": "certificate.expiring",
  "documentType": null,
  "timestamp": "2026-05-06T10:15:55.009Z",
  "data": {
    "tenantId": "tnt_3d9...",
    "participantId": "5a4f1e7f-95c7-4f7b-bec4-45b3f7e9db93",
    "certificateId": "b6f39c9a-6467-4f53-9598-5f1dd22f9f61",
    "expiresAt": "2026-06-05T00:00:00.000Z",
    "daysRemaining": 30
  }
}

Best practice for reliable incoming processing

The safest approach is: use webhook as trigger, then read inbox, then acknowledge.

  1. Listen for document.received (and optionally mlr) webhook events.
  2. After receiving webhook, call one of inbox endpoints:
    • GET https://api.pepposh.eu/api/documents/inbox
    • GET https://api.pepposh.eu/api/participants/{participantId}/documents/inbox
    • GET https://api.pepposh.eu/api/tenants/{tenantId}/participants/{participantId}/documents/inbox
  3. Process each document and then call POST https://api.pepposh.eu/api/documents/{documentId}/mlr/confirm.

Why this is reliable: inbox endpoints return documents that are not acknowledged yet, so even if webhook delivery fails, your polling/read-after-trigger flow can still recover all pending items.

Retries, delivery semantics, and idempotency

  • Webhook delivery is retried 5 times after the initial attempt (6 attempts total).
  • Backoff schedule is 10s, 20s, 40s, 80s, and 160s after failed attempts 1 through 5.
  • If all attempts fail, delivery is marked as failed in server logs/events.
  • Design your receiver as idempotent: deduplicate by event envelope id and/or business tuple (event, documentId, messageId, status).
  • Verify X-Webhook-Signature against your webhook secret before processing payload.