Webhooks
Table of contents
Webhooks allow CloudBilling to notify an external system when an event occurs in relation to an invoice. You configure a web address (URL) in CloudBilling. Whenever an invoice is calculated, submitted for approval or approved, CloudBilling sends an HTTP POST request containing a JSON message to that address. The receiving system can then act on the event, for example by performing its own validation of the invoice or by exporting the invoice to a bookkeeping system.
This page is intended for administrators who configure webhooks in the CloudBilling portal and for developers who implement the receiving endpoint.
A message may be delivered more than once. CloudBilling retains each message until delivery has been confirmed and retries failed deliveries. As a result, the receiving system may receive the same message more than once, in rare cases even after it has already acknowledged that message. The receiving system must therefore process messages idempotently: receiving a message a second time must not cause the associated work to be performed again. Use the combination of eventType, invoiceId, revisionToken and occurredAt to identify messages that have already been processed. See Messages may be delivered more than once.
Before you start
Availability
Webhooks are an optional feature that CloudBilling enables per environment. If the Webhooks option is not present in the Administration menu of your environment, contact CloudBilling through the support portal or by e-mail to support@cloudbilling.nl to request that webhooks be enabled.
Permissions
Access to the webhook pages requires that both of the following conditions are met:
- The user group has the Administrator role, or it holds the view permission for Environment Settings. This is the same permission that grants access to the Environment Settings page; a user who can open Environment Settings can also open Webhooks.
- The user group is not restricted to a customer cluster. If Customer Cluster Permission Scoping is active in the environment, only users whose group covers all customers can access the webhook pages.
All users with access to the webhook pages can create, modify and delete subscriptions and can view the delivery log.
See User Groups for information on assigning permissions.
Location in the portal
Open the Administration menu (the cogwheel icon in the menu bar) and select Webhooks.
Managing subscriptions
A subscription defines where CloudBilling sends messages and which events are included. Each enabled subscription receives its own copy of every event it is subscribed to. Subscriptions are managed on the Webhook subscriptions page.
Subscription fields
| Field | Required | Description |
|---|---|---|
| Name | No | A label for your own reference. |
| URL | Yes | The address to which CloudBilling sends the messages. It must be an absolute address with the https:// scheme, for example https://integration.example.com/cloudbilling/webhook. Addresses that use http:// and relative addresses are rejected with the message Webhook URL must be an absolute https:// URL. |
| Enabled | Default: on | When this option is cleared, CloudBilling sends nothing to the subscription. |
| Event types | No | Select the events the subscription should receive. See Events and messages. |
| Headers | No | Additional HTTP headers that CloudBilling includes in every message sent to the subscription. This is where the shared secret is configured; see below. |
Headers and the shared secret
The subscription carries one or more headers with secret values known only to you and CloudBilling. The receiving system verifies these headers on every request and rejects requests in which a header is missing or incorrect. This is how the receiving system establishes that a request originates from CloudBilling.
Enter a header name, for example X-CB-Secret, and a long random value. CloudBilling sends the name and value as entered.
Please note the following:
- Header values are stored as entered and are displayed in plain text to every user who can open the subscription. Treat the subscription page as confidential.
- Do not include secrets in the URL. The URL is displayed in the list of subscriptions and in every row of the delivery log.
Changing, suspending or deleting a subscription
Changes to a subscription apply to all messages sent from that moment on, including retries of earlier messages. This also applies to the URL: a retry is sent to the URL as configured at the moment of the retry.
Messages that are still queued for a subscription that has been suspended or deleted in the meantime are discarded. They are not sent and do not appear in the delivery log.
Events and messages
Every message is an HTTP POST request. The body is a JSON document, sent with Content-Type: application/json; charset=utf-8.
Fields present in every message
| Field | Type | Description |
|---|---|---|
eventType | string | The event that occurred. One of the values listed in the next table. |
occurredAt | string (date and time) | The moment at which CloudBilling created the message, in ISO 8601 format. The value is always in UTC and always carries the numeric offset +00:00 (never the Z form), for example 2026-06-05T10:00:00.1234567+00:00. |
invoiceId | string | The unique identifier of the invoice in CloudBilling. It is the same identifier that the API uses, for example in GetInvoiceDetails, and it remains the same when the invoice is recalculated. |
revisionToken | string or null | Identifies the calculation result of the invoice at the time of the event. Every recalculation produces a new token. The value is null if the invoice has not been calculated yet. |
Event types
| Event type | When CloudBilling sends it | Additional fields |
|---|---|---|
invoice.calculated | An invoice has been calculated successfully. This occurs during a billing run, when an invoice is recalculated and when a credit invoice is created. Every recalculation produces a new message with a new revisionToken. | none |
invoice.approval-started | Approval has been requested for an invoice, either in the portal or through the API method ApproveInvoices. The invoice is now in the status Approving. | none |
invoice.approved | The final step of the approval process has been completed. The invoice is now in the status Approved and has been assigned an invoice number. | invoiceNumber (string or null): the assigned invoice number. approvedAt (string, date and time in UTC): the moment of approval. |
See Invoice Approval for a description of the approval process.
Examples
An invoice.calculated message:
{
"eventType": "invoice.calculated",
"occurredAt": "2026-06-05T10:00:00.1234567+00:00",
"invoiceId": "683f2a1c9d1e4b0007a1b2c3",
"revisionToken": "8b1c9f3e-5a2d-4c7b-9e0f-1a2b3c4d5e6f"
}
An invoice.approved message:
{
"invoiceNumber": "2026-0001",
"approvedAt": "2026-06-05T09:59:58.417+00:00",
"eventType": "invoice.approved",
"occurredAt": "2026-06-05T10:00:00.1234567+00:00",
"invoiceId": "683f2a1c9d1e4b0007a1b2c3",
"revisionToken": "8b1c9f3e-5a2d-4c7b-9e0f-1a2b3c4d5e6f"
}
Notes for developers:
- Additional fields may be introduced in the future. Ignore unknown fields.
- The message identifies the invoice. Use the
invoiceIdto retrieve invoice details through the API, for example with GetInvoiceDetails.
Delivery
Messages are queued and sent by a background process shortly after the event occurs.
Responding to a message
- The receiving system must respond with an HTTP status code in the 2xx range (for example
200 OKor204 No Content) to acknowledge receipt. Any other status code is treated as a failed attempt. - CloudBilling waits at most 30 seconds for a response. If no response is received within this period, the attempt is recorded as a timeout. Acknowledge promptly and perform any time-consuming processing after responding.
Retries
When an attempt fails, CloudBilling retries the delivery at a later time. An attempt is considered failed when:
- the receiving system responds with a status code outside the 2xx range, including
4xxcodes such as401or404; - the receiving system does not respond within 30 seconds;
- no connection could be established, for example because the address cannot be resolved or the TLS certificate is invalid.
CloudBilling makes at most 10 attempts in total, with increasing intervals between attempts. After the final failed attempt, CloudBilling abandons the message. All attempts remain visible in the delivery log.
Every attempt sends the same message body, including the same occurredAt value. The URL and the headers are taken from the subscription as configured at the moment of the attempt.
Messages may be delivered more than once
The delivery mechanism is designed so that a message is not lost when a failure occurs on either side. The consequence is that a message may be delivered more than once. This can occur in two situations:
- The receiving system received and processed the message, but its response did not reach CloudBilling within the time limit. CloudBilling records a timeout and sends the message again.
- The background process was interrupted between receiving the response and marking the message as delivered. The message is sent again after the process restarts.
Consequences for the receiving system:
- Process every message idempotently. Receiving a message a second time must not cause the associated work to be performed again. For example, when a message triggers an export, first verify whether the export has already been performed for this
invoiceIdandrevisionToken. - Identify repeated messages. A repeated message has exactly the same body as the original. Use the combination of
eventType,invoiceId,revisionTokenandoccurredAtto identify a message that has already been processed. - A new
revisionTokenis a new event. When an invoice is recalculated, CloudBilling sends a newinvoice.calculatedmessage with a newrevisionToken. This concerns a newer version of the invoice and must be processed. - Messages may arrive out of order. Because failed attempts are retried with increasing intervals, an older message may arrive after a newer one. When sequence matters, use
occurredAtandrevisionToken.
The delivery log
Open a subscription and select Deliveries to view the log for that subscription. The log shows the 100 most recent attempts, newest first. Every attempt, including every retry, is recorded as a separate row.
| Column | Meaning |
|---|---|
| Time | The moment of the attempt, in UTC, for example 2026-06-05 10:00:00Z. |
| Event | The event type, for example invoice.approved. |
| Correlation | The invoiceId of the message. Use it to find all attempts related to one invoice. |
| URL | The address to which the attempt was sent. |
| Outcome | Success: the receiving system responded with a 2xx status code. HttpError: the receiving system responded with another status code. Timeout: no response within 30 seconds. Failed: no connection could be established, or another error occurred. |
| Status | The HTTP status code returned by the receiving system. Displayed as — when no response was received. |
| Duration (ms) | The duration of the attempt in milliseconds. |
| Error | Details for unsuccessful attempts, for example the status code or the connection error. |
Security guidance for the receiving system
- Verify the shared secret on every request. Compare the value of the secret header (for example
X-CB-Secret) with the configured value. Reject requests in which the header is missing or differs, for example with status401. CloudBilling retries rejected requests according to the schedule above, so an incorrect secret appears in the delivery log as a series ofHttpErrorattempts. - Never expose a receiving endpoint without this verification. Otherwise, anyone who knows the address could send fabricated messages to the receiving system.
- Use HTTPS with a valid certificate. CloudBilling sends only to
https://addresses, so the message and the secret header are always encrypted in transit. If the certificate is invalid or expired, deliveries fail with the outcomeFailed. - Treat the message as untrusted input. Validate the fields before use. Retrieve the authoritative invoice data through the API rather than relying on values in the message.
- Expect retries and repeated messages. See Messages may be delivered more than once.
- Restrict access to the subscription page. The secret is displayed there in plain text.
Using webhooks with approval checks
Webhooks inform an external system that an invoice has changed. With approval checks, the external system can in turn inform CloudBilling whether an invoice may be approved. Together they form a round trip:
- CloudBilling calculates an invoice and sends
invoice.calculated, or approval is requested and CloudBilling sendsinvoice.approval-started. - The external system validates the invoice, for example against CRM or contract data.
- The external system posts the result of the check to CloudBilling through the API.
- CloudBilling permits approval of the invoice only when every enabled check has passed.
Approval checks are a separate optional feature that CloudBilling enables per environment, independently of webhooks. If the Approval Checks option is not present in the Administration menu, contact CloudBilling support.
Defining approval checks
Open the Administration menu and select Approval Checks. This page requires the Administrator role. A check has the following fields:
| Field | Description |
|---|---|
| Key | The technical identifier that the external system uses when posting a result, for example crm-contract-check. The key must be unique and cannot be changed after the check has been created. |
| Name | The name displayed to users on the invoice page. |
| Description | An optional explanation of the purpose of the check. |
| Enabled | Only enabled checks block approval and accept results. |
Effect on the approval process
As long as an enabled check has no passed result for the current calculation of an invoice, the invoice cannot be approved. The final step of the approval process fails with the message Invoice approval is blocked by approval checks, followed by the invoice identifier and the checks that have not passed. Each such check is either Pending (no result has been posted) or Failed (a failing result has been posted).
On the invoice page, the section Approval checks shows the state of every enabled check for that invoice (Pending, Passed or Failed), together with the reason provided by the external system. Every posted result is also recorded in the activity of the invoice.
Posting a check result
The external system posts a result with the API method PostApprovalCheckResult. As with every API call, an API token is required. See Understanding API Security and API Tokens.
The input, ApprovalCheckResultInput, has the following fields:
| Field | Description |
|---|---|
InvoiceId | The invoiceId from the webhook message. |
CheckKey | The key of the approval check as defined in the portal. |
RevisionToken | The revisionToken from the webhook message. It indicates the calculation of the invoice to which the result applies. |
Passed | true if the invoice passed the check, false if it did not. |
Reason | Optional. A short explanation, displayed on the invoice page and recorded in the activity of the invoice. |
CloudBilling applies the following rules:
- The check must exist and be enabled; otherwise the result is rejected.
- The
RevisionTokenmust match the current calculation of the invoice. If the invoice has been recalculated since the message was received, the result is rejected as stale. Await the newinvoice.calculatedmessage and validate the invoice again. - Once an invoice has the status Approved, results for that invoice are no longer accepted.
- A new result for the same check and the same calculation replaces the earlier result. This allows the external system to validate an invoice again, for example after
invoice.approval-started.
The API reference displays PostApprovalCheckResult and PostInvoiceExportResult with an Obsolete label. For these two methods, the label reflects their status: they are under development, not yet stable, and subject to change without notice.
Reporting an export result
When the external system exports an invoice to another system, for example a bookkeeping system, it can report the outcome with the API method PostInvoiceExportResult. The input, InvoiceExportResultInput, has the fields InvoiceId, Success, Reason (optional) and ExternalReference (optional, for example the document number in the other system). On the invoice page, the section Bookkeeping export then displays Succeeded or Failed, the reason, the reference and the moment at which the result was reported. A new result for the same invoice replaces the earlier one. The result is also recorded in the activity of the invoice. This method also requires the approval checks feature to be enabled.