For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.

Webhook deliveries are made by Qvalia to your endpoint and do not count against your API rate limit.

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

Field
Type
Notes

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

Field
Type
Notes

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

Value
Terminal?
Meaning

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.

Field
Notes

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. Respond 2xx; 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, with GET …/webhook/{eventId} — see Webhooks. Webhooks still cannot be your only source of truth: keep polling GET /transaction/… as a safety net. Signing and eventId deduplication 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 in status.updatedAt, so do not dedupe on the whole body.)

  • Ordering is not guaranteed. A document_delivery can arrive before the new_document for the same document. Use occurredAt to 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's parentAccount is the partner and the partner holds the webhook scope).

Last updated

Was this helpful?