Webhook payload
An overview of the Qvalia Webhook format
Verify before you trust
Once the subscription has a signing secret, every delivery carries X-Qvalia-Signature.
Verify it before acting on the payload — see
Webhooks for the full procedure.
Sign the exact bytes you received, not a re-serialised JSON object. That is the most common cause of a failing first implementation.
The JSON body delivered to a subscriber's url:
{
"eventId": "4d1c9f84-3a2b-5c6d-9e0f-1a2b3c4d5e6f",
"eventType": "document_delivery",
"accountRegNo": "SE5560004755",
"documentType": "Invoice",
"direction": "outgoing",
"integrationId": "6b928ef1-fb0d-4b9a-a56f-b5dbca7a0fd4",
"occurredAt": "2026-08-19T09:26:10.104Z",
"documentId": "123456-INV",
"globalTransactionId": "6b928ef1-fb0d-4b9a-a56f-b5dbca7a0fd4",
"status": {
"status": "processed",
"event": "message-log/update",
"deliveryMethod": "peppol",
"updatedAt": "2026-08-19T09:26:09.881Z"
},
"error": null,
"peppol_metadata": { "…": "…" }
}Top level
eventId
uuid
Identifies the event, not the delivery attempt. Remains the same across redeliveries; dedupe on this. Also sent as X-Qvalia-Event-Id.
eventType
enum
new_document | document_delivery | document_delayed | document_error. Always present.
accountRegNo
string
The account the document belongs to. Always present.
documentType
string
Invoice, CreditNote, Order, etc. Follows Transaction API.
direction
enum
incoming | outgoing from account point of view.
integrationId
uuid
Qvalia's document id — use it to GET the document over the API.
occurredAt
ISO-8601
When the event happened upstream.
documentId
string
Document number (e.g. invoice number). Usually on new_document; often absent on the later status events.
globalTransactionId
uuid
Platform-wide id for the whole document exchange.
status
object
Delivery-lifecycle detail — see below.
error
string
Human-readable failure reason. Present on document_error.
peppol_metadata
object | null
Peppol exchange detail recorded for the document so far; null unless status.deliveryMethod is peppol, {} when none is known yet.
The status object
status
string
Delivery status from the upstream channel. Open set, see below. May be absent.
event
enum
message-log/create
deliveryMethod
string
How the document was sent. Observed: peppol, print, email, api, plus channel-specific values such as kivra, billo, minameddelanden, internetbank, postal, dynamic_routing. May be absent.
updatedAt
ISO-8601
When we recorded this status. Differs between redeliveries of the same event.
Statuses
pending
no
Logged; nothing attempted yet. Also the message_log.status column default.
delayed
no
Being retried: a Peppol send still inside its retry window. A terminal status follows; do not treat it as a failure.
warning
no
Reopened by a rules-processor reset or Quarantined document.
processed
yes
Delivered successfully.
processed_with_warning
yes
Delivered, but something about the exchange is worth flagging.
error
yes
Delivery failed. The top-level error carries the reason.
(absent)
—
No status reported for this event. Documents entering through the Public API announce themselves without one.
Which statuses go with which event:
eventType
status.event
Typical status.status
new_document
message-log/create
pending, or absent
document_delayed
message-log/delayed
delayed
document_delivery
message-log/update
processed, processed_with_warning, error
document_error
message-log/error
error
error also arrives on document_delivery: the event type and the reported status are set independently.
Telling a retry from a real failure
Branch on the event type, not the text of error:
document_delayed— still inside the retry window. Another event is coming.document_error— stopped deliveries if retries are exhausted, may be retry-able from Qvalia supportive functions.
A struggling Peppol send produces one document_delayed per retry cycle before settling on document_delivery or document_error. Leave document_delayed out of your subscription's types if you do not want that traffic; the terminal events arrive either way. Each cycle has its own eventId, so they do not dedupe against each other.
peppol_metadata
Present when status.deliveryMethod is peppol, null otherwise. Every event about a Peppol document carries the exchange detail recorded for it so far, not only the event that reported it — a later status report fills it in from what was stored when the document was sent or received. {} means nothing is known yet, e.g. a retry report before any exchange succeeded.
messageId
Peppol message id, e.g. 9cab8ba5-…@QVALIA-PSE000094
accessPoint
Sending Access Point id (C2), e.g. PSE000094
docTypeId
Peppol document type identifier
processId
Peppol process identifier
exchangeDateTime
When the exchange happened
senderPeppolId
Sender's Peppol participant id
receiverPeppolId
Receiver's Peppol participant id
transactionId is omitted; it duplicates globalTransactionId.
How stable eventId is
A UUIDv5 over eventType, accountRegNo, documentType, direction, integrationId, documentId, globalTransactionId, status.status and occurredAt.
status.updatedAt is not part of it: Qvalia re-stamp it on each internal processing attempt, so including it would give a reprocessed event a new id.
occurredAt is, and separates repeats of the same transition — successive retry cycles report the same status and differ only in when they happened.
An event arriving with no occurredAt gets a random id instead, and is logged. Treat it as a distinct event. Everything from the message-log pipeline carries occurredAt.
Delivery semantics
POST,Content-Type: application/json. Respond2xx; time out after 10 seconds.A failed delivery is not retried. A non-2xx, timeout or unreachable endpoint is recorded against the document and not attempted again.
A missed event can be fetched again for 30 days, by its
eventId, withGET …/webhook/{eventId}— see Webhooks. Webhooks still cannot be your only source of truth: keep pollingGET /transaction/…as a safety net. Signing andeventIddeduplication make a delivery you receive trustworthy; neither makes delivery guaranteed.You may still receive the same event twice. An internal retry upstream, or a document published by more than one producer, can re-deliver it. Dedupe on
eventId: it is derived from the event's identity, so redeliveries share it. (Redeliveries also differ instatus.updatedAt, so do not dedupe on the whole body.)Ordering is not guaranteed. A
document_deliverycan arrive before thenew_documentfor the same document. UseoccurredAtto order, not arrival time.A subscription covers exactly one account. A partner-level subscription (
/partner/{partnerRegNo}/webhook) receives the partner's own events only — not its children's; a partner subscribes to each child individually with/partner/{partnerRegNo}/account/{accountRegNo}/webhook.An account and its partner can each subscribe to the same account. The two are separate subscriptions, and an event for the child is delivered to both, carrying the same
eventId. A partner's subscription to a child only delivers while the partner may act for it (the child'sparentAccountis the partner and the partner holds thewebhookscope).
Last updated
Was this helpful?

