Webhooks
Webhooks allow your application to receive real-time notifications when events occur in the Oxygen Peppol system. Instead of polling the API, you can configure webhooks to automatically receive updates about invoice status changes, transmissions, and acknowledgments.
Overview
When an event occurs, Oxygen Peppol sends an HTTP POST request to your configured webhook URL with a JSON payload containing event details.
Key Features
- Real-time notifications – Receive instant updates without polling
- Secure delivery - HMAC signature verification
- Automatic retries – Failed deliveries are retried with exponential backoff
Registering webhooks
To register a webhook, you must expose an endpoint in your application that Oxygen can call. Webhooks can be configured through the Oxygen dashboard under the API settings section. When creating a webhook, assign a descriptive name, select the event types you wish to subscribe to, and provide the callback URL.
Once configured, Oxygen will automatically send webhook requests to your endpoint whenever the selected events occur.
Consuming webhooks
The Oxygen Peppol API sends webhook notifications as HTTP POST requests whenever an invoice-related event occurs. Each webhook request includes a JSON-formatted payload containing details about the event that triggered the notification, allowing your system to react accordingly.
{
"event": "invoice.status_updated",
"id": "",
"invoice_number": "",
"response_code": "",
"reason_code": "",
"reason": "",
"status_code": "",
"error_message": "",
"created_at": ""
}In the example above, an invoice entity was updated, and the payload contains the corresponding invoice identifier along with the new status information.
Payload fields
| Field | Type | Description |
|---|---|---|
event | string | The type of event that triggered the webhook |
id | string | The unique identifier of the invoice associated with the event. |
invoice_number | string | The invoice number as provided during invoice creation. |
response_code | string | The response code received from the C3 corner (if applicable). |
reason_code | string | The reason code associated with the invoice status update (if applicable). |
reason | string | A human-readable description of the reason for the status update (if applicable). |
status_code | int | The new status code of the invoice (e.g., 201, 202, 417). |
error_message | string | An error message providing additional context if the invoice encountered an error during transmission. |
created_at | string | The timestamp indicating when the event occurred. |
Payload example for Acknowledgment
When an invoice is acknowledged by the C3 corner, the webhook payload will look like this:
{
"event": "invoice.status_updated",
"id": "01kev9gvvrwggfmbjb3zg4qvb3",
"invoice_number": "888888888|14/01/2026|0|1.1|ΤΔΑ|14",
"transmission": {
"id": "01kev9gpprwggfmbjb3zg4qvkk",
"response_code": "AB",
"reason_code": "OTH",
"reason": "OK",
"status_code": 200,
"error_message": null
},
"created_at": "2024-06-15T12:34:56Z"
}Payload example for Rejection
When an invoice is rejected by the final recipient, the webhook payload will look like this:
{
"event": "invoice.status_updated",
"id": "01kev9gvvrwggfmbjb3zg4qvb3",
"invoice_number": "888888888|14/01/2026|0|1.1|ΤΔΑ|14",
"transmission": {
"id": "01kev9gpprwggfmbjb3zg4qvkk",
"response_code": "RE",
"reason_code": "OTH",
"reason": "Invalid total net amount",
"status_code": 406,
"error_message": null
},
"created_at": "2024-06-15T12:45:00Z"
}Security
To ensure that webhook requests are genuinely sent by Oxygen and not by a third party, each webhook request includes a signature header named X-Oxygen-Signature. This signature is generated using an HMAC SHA-256 hash of the request payload and your webhook’s secret key.
You should always verify this signature before processing the webhook payload.
$rawBody = file_get_contents('php://input');
$receivedSignature = $_SERVER['HTTP_X_OXYGEN_SIGNATURE'] ?? '';
$computedSignature = 'sha256=' . hash_hmac('sha256', $rawBody, $yourSecret);
if (hash_equals($computedSignature, $receivedSignature)) {
// Valid webhook from Oxygen
$data = json_decode($rawBody, true);
}use Illuminate\Http\Request;
// In your controller method
public function update(Request $request)
{
$rawBody = $request->getContent();
$receivedSignature = $request->header('X-Oxygen-Signature') ?? '';
$computedSignature = 'sha256=' . hash_hmac('sha256', $rawBody, $yourSecret);
if (hash_equals($computedSignature, $receivedSignature)) {
// Valid webhook from Oxygen
$data = json_decode($rawBody, true);
}
}If the computed signature matches the X-Oxygen-Signature header, the request can be trusted as originating from Oxygen. It is critical to keep your webhook secret secure. Do not commit webhook secrets to source control or expose them publicly, as doing so compromises the integrity of your webhook verification process.