Peppol Delivery Status: Webhooks vs Polling, MLS vs MLR

“Delivered” on Peppol means two different things, and most integration bugs come from conflating them. The transport acknowledgment – the message-level status exchanged between Access Points over AS4 – confirms the recipient’s Access Point accepted your document: routing worked, the envelope validated, custody transferred. It says nothing about the buyer. The business response – the Invoice Response, an `ApplicationResponse` UBL document travelling back from the buyer’s side – is what tells you the invoice is in process, accepted or rejected. The first is system-generated and fast; the second depends on the buyer’s software and habits, and in many markets simply never arrives. Design your status model around that honesty: transport state is guaranteed, business state is a bonus.

What states should your application track?

A practical minimum is five: submitted (your API call accepted after validation), delivered (recipient AP acknowledged over AS4), delivery-failed (routing or transport error – the recipient may not receive documents of this type at all), business-accepted and business-rejected (from an Invoice Response, when one comes). Treat the business states as optional enrichments with their own timestamps rather than mandatory steps in one pipeline: an invoice that stays “delivered” forever is normal in markets where buyers do not send responses.

Webhooks or polling?

Both, with clear roles. Webhooks are the primary channel: the provider pushes transport and business events to your endpoint as they happen, which is the only way to get timely status without hammering an API. Polling a status endpoint is the reconciliation channel: a scheduled sweep that catches anything a webhook missed; because webhooks will occasionally be missed, whatever the provider. Two implementation rules keep this robust. Verify signatures: providers sign webhook deliveries (Finbite’s API, for instance, signs them with HMAC), and an unverified webhook endpoint is an open door for forged status. And make handling idempotent: redeliveries happen by design, so processing the same event twice must be harmless.

What does a rejection actually tell you?

Depends on the layer. A transport-level failure is infrastructural: wrong or unregistered recipient, document type not accepted, network fault; fix the addressing or check the recipient’s registration. A business-level rejection is commercial: the buyer’s system or accountant declined the invoice content; wrong PO reference, unexpected amounts, their internal rules. The first belongs to your engineering backlog, the second to your user’s workflow. Surfacing the two identically is how support tickets are born.

Does ‘delivered’ mean the buyer accepted my Peppol invoice?

No. Transport acknowledgment means the recipient’s Access Point accepted the message. Buyer acceptance or rejection arrives, if at all, as a separate business-level Invoice Response.

What is the difference between MLS and MLR?

Message-level status is the system-generated transport acknowledgment between Access Points; the Invoice Response (business response) is a UBL ApplicationResponse from the buyer’s side stating in-process, accepted or rejected.

Should I use webhooks or polling for Peppol status?

Webhooks as the primary push channel, polling as scheduled reconciliation for anything missed. Verify webhook signatures (e.g. HMAC) and keep handlers idempotent.

Is the business response mandatory on Peppol?

No — adoption varies by market and buyer software. An invoice that remains ‘delivered’ without a business response is a normal outcome; model it as such.