Skip to main content

Webhooks

Invostaq sends webhook events when invoice status changes on the Peppol network. Set up a webhook endpoint to receive delivery confirmations, failure notifications, and inbound invoices in real time.


Prerequisites

  • An active Invostaq account
  • A publicly accessible HTTPS endpoint that can receive POST requests

Setup

Register your webhook URL from the Invostaq dashboard under Settings > Webhooks. Provide:

  • URL — your HTTPS endpoint (e.g., https://your-app.com/webhooks/invostaq)
  • Secret — a shared secret for validating webhook authenticity
  • Name — a label to identify this webhook in the dashboard

Your endpoint must respond with 200 OK to every webhook delivery. The response body is ignored.


Events

transaction.sent — Invoice delivered

The invoice was successfully delivered to the recipient's Access Point.

{
"transactionId": "txn_abc123def456",
"eventType": "transaction.sent",
"status": "Delivered"
}

Effect: The invoice status updates from Processing to Delivered.


transaction.failed — Delivery failed

The Peppol network rejected or could not deliver the invoice.

{
"transactionId": "txn_abc123def456",
"eventType": "transaction.failed",
"status": "Failed",
"reason": "Recipient Access Point unreachable"
}

Effect: The invoice status updates to Failed with the failure reason.


transaction.received — Inbound invoice

A trading partner sent an invoice to your Peppol participant ID.

{
"eventType": "transaction.received",
"payload": {
"transactionId": "inbound-tx-67890",
"receiverId": "0196:971501234567",
"docInstanceId": "doc-instance-id"
},
"senderParticipantId": "0088:9876543210987",
"timestamp": "2024-06-15T10:30:00Z"
}

Effect: Invostaq fetches the UBL XML, archives the original document, parses the invoice metadata, and creates a new invoice record with status Received.


webhooks.test — Connectivity test

Sent when you first configure a webhook URL. No action required — just respond with 200 OK.

{
"eventType": "webhooks.test",
"timestamp": "2024-06-15T09:01:00Z"
}

Security

Secret validation

Webhook deliveries include a secret header that you should validate before processing:

X-Webhook-Secret: your_configured_secret

Use a timing-safe comparison to prevent timing attacks:

import { timingSafeEqual } from "crypto";

function verifyWebhookSecret(received: string, expected: string): boolean {
const a = Buffer.from(received);
const b = Buffer.from(expected);
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}

Retry behavior

ScenarioResponseResult
Valid event, processed successfully200 OKEvent recorded
Invalid secret or malformed JSON200 OKSilently dropped (prevents retry loop)
Internal processing failure500Peppol Access Point retries delivery

Why 200 OK for invalid events? The Peppol Access Point retries on non-2xx responses. Returning 200 for permanently bad events prevents infinite retries that would never succeed.


Invoice status flow

Outbound:
Draft → Processing → Delivered
→ Failed

Inbound:
Received → Approved → Synced
→ Rejected
StatusMeaning
ProcessingSubmitted to Peppol, awaiting Access Point response
DeliveredSuccessfully delivered to recipient (via transaction.sent)
FailedDelivery failed (via transaction.failed)
ReceivedInbound invoice received from trading partner

Best practices

  • Always return 200 OK — don't return errors from your webhook handler, even if your internal processing fails. Queue the event for async processing instead.
  • Make your handler idempotent — you may receive the same event more than once. Use transactionId to deduplicate.
  • Process quickly — return 200 OK within a few seconds. If you need to do heavy processing, enqueue the event and handle it asynchronously.
  • Validate the secret — always check the X-Webhook-Secret header before trusting the payload.

Common mistakes

MistakeWhat happensFix
Returning non-200 for invalid eventsInfinite retry loop from Access PointAlways return 200 OK, even for bad events
Not deduplicating eventsSame invoice processed twiceCheck transactionId before processing
Synchronous heavy processingWebhook timeout, missed eventsEnqueue and process asynchronously
Comparing secrets with ===Vulnerable to timing attacksUse timingSafeEqual