Webhook API
Operations related to Web hooks
An API to fetch (GET) the webhook subscription for your Partner account.
Partner or Partners Account
Partners can configure their own web hooks through /partner/{partnerRegNo}/webhook/... URI, or their child accounts using /partner/{partnerRegNo}/account/{accountRegNo}/webhook/.... The latter is useful if you want to scope the subscription to a single account. A partner-level subscription covers the partner's own account only, not its children: to receive a child's events, subscribe to that child with /partner/{partnerRegNo}/account/{accountRegNo}/webhook/..., one child at a time. A partner's subscription to a child is separate from the child's own subscription (neither can see or change the other's), and an event for the child is delivered to both, with the same eventId. It delivers only while the partner may act for the child (the child's parentAccount is the partner and the partner holds the webhook scope), which is checked on every delivery.
parameter accountRegNo is only required for child accounts, and should be omitted for the Partner account itself.
Account registration number issued by Qvalia
OK
a1b2c3d4-5678-90ab-cdef-1234567890abhttps://example.com/webhooks/qvalia["new_document","document_delivery"]SE1234567890trueapi_keyPossible values: 2024-01-01T12:00:00.000Z2024-01-02T12:00:00.000ZNo Content. The request succeeded and there is no body to return.
Unauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
GET /account/{accountRegNo}/webhook/configure HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Accept: */*
{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"url": "https://example.com/webhooks/qvalia",
"types": [
"new_document",
"document_delivery"
],
"accountRegNo": "SE1234567890",
"enabled": true,
"authType": "api_key",
"signing": {
"algorithm": "hmac-sha256",
"secrets": [
{
"id": "whsk_91be0c4d22af",
"createdAt": "2026-09-14T09:12:44.031Z",
"expiresAt": "2026-09-15T09:12:44.031Z",
"active": true
}
]
},
"createdAt": "2024-01-01T12:00:00.000Z",
"updatedAt": "2024-01-02T12:00:00.000Z"
}An API to create or update (PUT) the webhook subscription for your account.
An account has a single webhook subscription. The first PUT creates it and returns a generated webhook id (a UUID v4); subsequent PUT requests update the same subscription (its url and/or types). Use the returned id with the /account/{accountRegNo}/webhook/{webhookId}/auth endpoints to attach outbound authentication.
A PUT that creates the subscription returns a signingSecret in clear text in that response only. Store it; see /account/{accountRegNo}/webhook/{webhookId}/secret to rotate it or to obtain one for an older subscription.
Event types
new_document— sent when a new document is received/created for an account.document_delivery— sent when a document's delivery status changes.document_delayed— sent when a delivery is being retried and has not finished.document_error— sent when a document's delivery fails.
Delivery (webhook usage)
Events are delivered as an HTTP POST with a JSON body to the configured url (which must be https). Your endpoint should respond with a 2xx status code; the delivery request times out after 10 seconds. If outbound authentication is configured (see /account/{accountRegNo}/webhook/{webhookId}/auth) it is applied as request headers on every delivery. The delivered payload is a single flat JSON object — see the webhookEvent callback below for the full schema, and /partner/{partnerRegNo}/webhook/configure in the Partner API for worked examples of all three event types.
Every delivery carries X-Qvalia-Event-Id, and X-Qvalia-Signature once the subscription has a signing secret — see /account/{accountRegNo}/webhook/{webhookId}/secret.
Delivery is best-effort: duplicates are possible, and a failed delivery is not retried. A missed event can be fetched again for 30 days with GET .../webhook/{eventId}, but keep polling the Transaction API as a safety net — see /partner/{partnerRegNo}/webhook/configure in the Partner API for details.
Account registration number issued by Qvalia
HTTPS endpoint that will receive webhook events
https://example.com/webhooks/qvaliaOK
a1b2c3d4-5678-90ab-cdef-1234567890abhttps://example.com/webhooks/qvalia["new_document","document_delivery"]SE1234567890Secret used to sign deliveries to this subscription (HMAC-SHA256).
Returned only when this PUT created the subscription, and only
in this one response — we store it encrypted and cannot show it
again. Store it now. Lost it, or updating a subscription created
before signing existed? Use
POST /account/{accountRegNo}/webhook/{webhookId}/secret.
whsec_Zx8QvK3mR7pL2nT9wY4hB6jF1sD0gA5cE8uI3oP7kMIdentifier for the secret above, for use in support and audit. Not itself a secret.
whsk_4f1a90c2b7dehmac-sha256Possible values: Unauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Unprocessable Entity
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
PUT /account/{accountRegNo}/webhook/configure HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 90
{
"url": "https://example.com/webhooks/qvalia",
"types": [
"new_document",
"document_delivery"
]
}{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"url": "https://example.com/webhooks/qvalia",
"types": [
"new_document",
"document_delivery"
],
"accountRegNo": "SE1234567890",
"signingSecret": "whsec_Zx8QvK3mR7pL2nT9wY4hB6jF1sD0gA5cE8uI3oP7kM",
"signingSecretId": "whsk_4f1a90c2b7de",
"signatureAlgorithm": "hmac-sha256"
}An API to DELETE the webhook subscription for your Partner account.
This removes the subscription and any attached outbound authentication.
Partner or Partners Account
Partners can configure their own web hooks through /partner/{partnerRegNo}/webhook/... URI, or their child accounts using /partner/{partnerRegNo}/account/{accountRegNo}/webhook/.... The latter is useful if you want to scope the subscription to a single account. A partner-level subscription covers the partner's own account only, not its children: to receive a child's events, subscribe to that child with /partner/{partnerRegNo}/account/{accountRegNo}/webhook/..., one child at a time. A partner's subscription to a child is separate from the child's own subscription (neither can see or change the other's), and an event for the child is delivered to both, with the same eventId. It delivers only while the partner may act for the child (the child's parentAccount is the partner and the partner holds the webhook scope), which is checked on every delivery.
parameter accountRegNo is only required for child accounts, and should be omitted for the Partner account itself.
Account registration number issued by Qvalia
No Content. The request succeeded and there is no body to return.
No content
Unauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
DELETE /account/{accountRegNo}/webhook/configure HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Accept: */*
No content
An API to fetch (GET) the outbound authentication type configured for your account's webhook subscription. The credential itself is never returned.
Account registration number issued by Qvalia
Webhook identifier returned by PUT /webhook/configure, e.g. "a1b2c3d4-5678-90ab-cdef-1234567890ab"
OK
api_keyPossible values: No Content. The request succeeded and there is no body to return.
Unauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
GET /account/{accountRegNo}/webhook/{webhookId}/auth HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Accept: */*
{
"type": "api_key"
}An API to set (POST) the outbound authentication applied to webhook deliveries for your Partner account's subscription.
{webhookId} must match the id returned by PUT /partner/{partnerRegNo}/account/{accountRegNo}/webhook/configure.
Provide exactly one of the supported auth types:
oauth— client-credentials. Qvalia fetches a Bearer token fromtokenUrlusingclientId/clientSecret(and optionalscope) at delivery time.api_key— sent as a request header (header, defaultX-API-Key) withvalue.basic— HTTP Basic auth usingusername/password. Thepasswordmust be at least 16 characters and contain an uppercase letter, a lowercase letter, a digit and a special character.
Account registration number issued by Qvalia
Webhook identifier returned by PUT /webhook/configure, e.g. "a1b2c3d4-5678-90ab-cdef-1234567890ab"
OK
api_keyPossible values: Unauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Not Found. The requested resource does not exist, or is not available to this account.
Unprocessable Entity
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
POST /account/{accountRegNo}/webhook/{webhookId}/auth HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 131
{
"type": "oauth",
"clientId": "text",
"clientSecret": "text",
"tokenUrl": "https://auth.example.com/oauth/token",
"scope": "webhooks:write"
}{
"type": "api_key"
}An API to DELETE the outbound authentication from your account's webhook subscription. The subscription itself is kept; deliveries are sent without auth headers afterwards.
Account registration number issued by Qvalia
Webhook identifier returned by PUT /webhook/configure, e.g. "a1b2c3d4-5678-90ab-cdef-1234567890ab"
No Content. The request succeeded and there is no body to return.
No content
Unauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
DELETE /account/{accountRegNo}/webhook/{webhookId}/auth HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Accept: */*
No content
An API to fetch (GET) which signing secrets exist on your account's webhook subscription and when each stops verifying.
This endpoint returns ids and dates only, never a secret value.
A secret is shown once, in the response that issues it: the PUT /account/{accountRegNo}/webhook/configure that creates the subscription, or POST /account/{accountRegNo}/webhook/{webhookId}/secret. It is stored encrypted and cannot be read back afterwards — if you have lost one, issue a new one with POST.
Account registration number issued by Qvalia
Webhook identifier returned by PUT /webhook/configure, e.g. "a1b2c3d4-5678-90ab-cdef-1234567890ab"
OK
a1b2c3d4-5678-90ab-cdef-1234567890abhmac-sha256Possible values: No Content. The request succeeded and there is no body to return.
Unauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
GET /account/{accountRegNo}/webhook/{webhookId}/secret HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Accept: */*
{
"webhookId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"algorithm": "hmac-sha256",
"secrets": [
{
"id": "whsk_91be0c4d22af",
"createdAt": "2026-09-14T09:12:44.031Z",
"expiresAt": "2026-09-15T09:12:44.031Z",
"active": true
}
]
}Issue a signing secret for your account's webhook subscription, or rotate an existing one. The same endpoint does both, and the plaintext secret is returned here and nowhere else.
{webhookId} must match the id returned by PUT /account/{accountRegNo}/webhook/configure.
Deliveries are then signed with HMAC-SHA256 (X-Qvalia-Signature). See POST /partner/{partnerRegNo}/webhook/{webhookId}/secret for how to verify a delivery, the rotation rules and the overlapHours window.
Account registration number issued by Qvalia
Webhook identifier returned by PUT /webhook/configure, e.g. "a1b2c3d4-5678-90ab-cdef-1234567890ab"
How long the secrets being replaced keep verifying, so you can deploy
the new one at your own pace. During the window every delivery carries
a v1= part for each active secret.
Use 0 when a secret has leaked: the old one stops verifying at once.
24Example: 24OK — the new signing secret. This is the only response that contains it.
a1b2c3d4-5678-90ab-cdef-1234567890abhmac-sha256Possible values: Identifier for the new secret. Not itself a secret.
whsk_91be0c4d22afThe new signing secret, in clear text. Store it now — it is not retrievable afterwards. Rotate again if you lose it.
whsec_Kd2MxW9fT4hR8nQ1vB6yL3jC7sZ0aE5gU2oI9pK4mN2026-09-14T09:12:44.031ZUnauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Not Found. The requested resource does not exist, or is not available to this account.
Unprocessable Entity
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
POST /account/{accountRegNo}/webhook/{webhookId}/secret HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 19
{
"overlapHours": 24
}{
"webhookId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"algorithm": "hmac-sha256",
"secretId": "whsk_91be0c4d22af",
"secret": "whsec_Kd2MxW9fT4hR8nQ1vB6yL3jC7sZ0aE5gU2oI9pK4mN",
"createdAt": "2026-09-14T09:12:44.031Z",
"previousSecrets": [
{
"id": "whsk_4f1a90c2b7de",
"expiresAt": "2026-09-15T09:12:44.031Z"
}
]
}An API to DELETE signing from your account's webhook subscription. Every secret is removed and deliveries are sent unsigned from the next event onwards. The subscription itself, and any outbound authentication on it, are kept.
Account registration number issued by Qvalia
Webhook identifier returned by PUT /webhook/configure, e.g. "a1b2c3d4-5678-90ab-cdef-1234567890ab"
No Content. The request succeeded and there is no body to return.
No content
Unauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
DELETE /account/{accountRegNo}/webhook/{webhookId}/secret HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Accept: */*
No content
Fetch a delivered webhook again by its eventId, e.g. one your endpoint missed. Every delivered body is kept for 30 days.
The response is the body exactly as it was POSTed to your url, same eventId included, so it deduplicates against a delivery you did receive. It is not signed: this request is authenticated like the rest of the API instead.
The eventIds of failed deliveries are listed on the document's GET /transaction/{accountRegNo}/{documents}/{direction}/status, under metadata.webhooks_dispatched, with the HTTP status/statusText your endpoint returned.
An event is served only for the account it is about (accountRegNo in the body). An unknown or expired eventId, or another account's, answers 204.
Account registration number issued by Qvalia
The eventId of a delivered webhook, as sent in the body and the X-Qvalia-Event-Id header, e.g. "4d1c9f84-3a2b-5c6d-9e0f-1a2b3c4d5e6f"
OK — the webhook body as it was delivered.
A webhook body, as POSTed to your endpoint and as returned by
GET /account/{accountRegNo}/webhook/{eventId}. Field reference: the webhookEvent
callback in the Partner API.
Identifies the event, not the delivery attempt. Derived from the
event content, so every redelivery of the same event carries the
same value — deduplicate on it. Also sent as the
X-Qvalia-Event-Id header.
4d1c9f84-3a2b-5c6d-9e0f-1a2b3c4d5e6fnew_documentPossible values: SE1234567890e.g. Invoice, CreditNote, Order
InvoiceQvalia unique identifier for the transaction/message
a1b2c3d4-5678-90ab-cdef-1234567890abincomingPossible values: 2024-01-01T12:00:00.000ZDocument number (e.g. invoice number). Present on new_document;
may be absent on document_delivery/document_error when the
upstream delivery channel does not report one.
123456-INVQvalia's platform-wide transaction id for this document exchange. Absent if not available for this event.
6b928ef1-fb0d-4b9a-a56f-b5dbca7a0fd4Error message describing the delivery failure. Only present when eventType is document_error.
Peppol validation failed: invoice does not conform to UBL 2.1No Content. The request succeeded and there is no body to return.
Unauthorized. The API key or Bearer token is missing, malformed, or not valid for the account in the URI.
Unprocessable Entity
Too Many Requests. The account has exceeded its request budget, which is counted per account across all Qvalia APIs.
Read Retry-After and wait at least that many seconds before retrying — retrying
immediately is simply rejected again. RateLimit-Remaining on every response tells
you how much of the budget is left, so a well-behaved client need not hit this at
all. Contact Qvalia Helpdesk if your integration needs
a higher limit.
Internal Server Error
GET /account/{accountRegNo}/webhook/{eventId} HTTP/1.1
Host: api-test.qvalia.com
Authorization: YOUR_API_KEY
Accept: */*
{
"eventId": "4d1c9f84-3a2b-5c6d-9e0f-1a2b3c4d5e6f",
"eventType": "new_document",
"accountRegNo": "SE1234567890",
"documentType": "Invoice",
"integrationId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"direction": "incoming",
"occurredAt": "2024-01-01T12:00:00.000Z",
"documentId": "123456-INV",
"globalTransactionId": "6b928ef1-fb0d-4b9a-a56f-b5dbca7a0fd4",
"status": {
"status": "processed",
"event": "message-log/create",
"deliveryMethod": "peppol",
"updatedAt": "2026-08-19T09:25:37.228Z"
},
"error": "Peppol validation failed: invoice does not conform to UBL 2.1",
"peppol_metadata": {
"messageId": "9cab8ba5-d2a4-45d0-842d-8ede91dcac9f@QVALIA-PSE000094",
"accessPoint": "PSE000094",
"docTypeId": "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",
"processId": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
"exchangeDateTime": "2026-08-19T09:25:34.226Z",
"senderPeppolId": "0007:5560004755",
"receiverPeppolId": "0007:5567321707"
}
}Last updated
Was this helpful?

