This guide explains how to set up and use webhooks to receive real-time notifications about Peppol document events using the Recommand API.
Overview
Webhooks allow your systems to receive instant notifications when new documents are received or other events occur in the Peppol network. Instead of constantly polling the API for updates, you can have updates pushed to your system as they happen.
Prerequisites
- A Recommand account with API access
- Your API key and secret
- Your team ID
- A publicly accessible URL endpoint on your server to receive webhook events
Setting Up a Webhook Endpoint
First, you need to create an endpoint on your server to receive the webhook events.
Node.js Example
// Using Express.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());
app.post("/peppol-webhook", (req, res) => {
const event = req.body;
// Process the event
console.log("Received webhook event:", event);
// Process new document
const docId = event.documentId;
// TODO: Fetch the document, process it, etc.
// Always respond with a 200 OK to acknowledge receipt
res.status(200).send("Event received");
});
app.listen(3000, () => {
console.log("Webhook server listening on port 3000");
});
PHP Example
<?php
// webhook-receiver.php
$payload = file_get_contents('php://input');
$event = json_decode($payload, true);
// Log the event
file_put_contents('webhook_log.txt', date('Y-m-d H:i:s') . ' - ' . $payload . PHP_EOL, FILE_APPEND);
// Process new document
$docId = $event['id'];
// TODO: Fetch the document, process it, etc.
// Always respond with a 200 OK to acknowledge receipt
http_response_code(200);
echo 'Event received';
Registering a Webhook
Once your endpoint is ready, register it with the Recommand API using the create webhook endpoint. Webhooks can also be created and managed from the dashboard.
async function createWebhook(teamId, webhookData) {
const response = await fetch(
`https://peppol.recommand.eu/api/peppol/${teamId}/webhooks`,
{
method: "POST",
headers: {
Authorization:
"Basic " +
Buffer.from("your_api_key:your_api_secret").toString("base64"),
"Content-Type": "application/json",
},
body: JSON.stringify(webhookData),
}
);
return response.json();
}
// Example usage
const webhookResult = await createWebhook("your_team_id", {
url: "https://your-domain.com/peppol-webhook",
companyId: null, // null means all companies, or specify a company ID
});
if (webhookResult.success) {
console.log(`Webhook created with ID: ${webhookResult.webhook.id}`);
} else {
console.error("Failed to create webhook:", webhookResult.errors);
}
Webhook Fields
Field | Description | Required | Example |
---|---|---|---|
url | Your webhook endpoint URL | Yes | "https://your-domain.com/peppol-webhook" |
companyId | Specific company to monitor (null for all) | No | "c_123" or null |
Listing Webhooks
To retrieve all webhooks associated with your team using the list webhooks endpoint:
async function listWebhooks(teamId) {
const response = await fetch(
`https://peppol.recommand.eu/api/peppol/${teamId}/webhooks`,
{
headers: {
Authorization:
"Basic " +
Buffer.from("your_api_key:your_api_secret").toString("base64"),
},
}
);
const result = await response.json();
return result.webhooks;
}
// Example usage
const webhooks = await listWebhooks("your_team_id");
webhooks.forEach((webhook) => {
console.log(`Webhook ID: ${webhook.id}, URL: ${webhook.url}`);
});
Updating a Webhook
To update an existing webhook using the update webhook endpoint:
async function updateWebhook(teamId, webhookId, updatedData) {
const response = await fetch(
`https://peppol.recommand.eu/api/peppol/${teamId}/webhooks/${webhookId}`,
{
method: "PUT",
headers: {
Authorization:
"Basic " +
Buffer.from("your_api_key:your_api_secret").toString("base64"),
"Content-Type": "application/json",
},
body: JSON.stringify(updatedData),
}
);
return response.json();
}
// Example usage
const updateResult = await updateWebhook("your_team_id", "webhook_id", {
url: "https://your-domain.com/updated-webhook-endpoint",
companyId: "c_123", // Change to monitor a specific company
});
if (updateResult.success) {
console.log("Webhook updated successfully");
} else {
console.error("Failed to update webhook:", updateResult.errors);
}
Deleting a Webhook
To delete a webhook using the delete webhook endpoint:
async function deleteWebhook(teamId, webhookId) {
const response = await fetch(
`https://peppol.recommand.eu/api/peppol/${teamId}/webhooks/${webhookId}`,
{
method: "DELETE",
headers: {
Authorization:
"Basic " +
Buffer.from("your_api_key:your_api_secret").toString("base64"),
},
}
);
return response.json();
}
// Example usage
const deleteResult = await deleteWebhook("your_team_id", "webhook_id");
if (deleteResult.success) {
console.log("Webhook deleted successfully");
} else {
console.error("Failed to delete webhook:", deleteResult.errors);
}
Webhook Event Types
When a document is received or other events occur, your webhook endpoint will receive a POST request with a JSON payload containing the event details.
Document Received Event
{
"eventType":"document.received",
"documentId":"doc_xxx",
"teamId":"team_xxx",
"companyId":"c_xxx"
}
Upon receiving this event, you would typically:
- Check the
eventType
to ensure it is a document received event - Retrieve the full document using the
documentId
via the get document endpoint - Process the document in your system
- Mark the document as read via the markAsRead endpoint
// Example of handling a received document event
app.post("/peppol-webhook", async (req, res) => {
const event = req.body;
if (event.eventType === "document.received") {
try {
// 1. Retrieve the document
const document = await fetchDocument(event.teamId, event.documentId);
// 2. Process the document in your system
await processDocumentInYourSystem(document);
// 3. Mark the document as read
await markDocumentAsRead(event.teamId, event.documentId);
console.log(`Successfully processed document ${event.documentId}`);
} catch (error) {
console.error(`Error processing document ${event.documentId}:`, error);
}
}
// Always acknowledge receipt
res.status(200).send("Event received");
});
// Fetch document function
async function fetchDocument(teamId, documentId) {
const response = await fetch(
`https://peppol.recommand.eu/api/peppol/${teamId}/documents/${documentId}`,
{
headers: {
Authorization:
"Basic " +
Buffer.from("your_api_key:your_api_secret").toString("base64"),
},
}
);
const result = await response.json();
if (!result.success) {
throw new Error(
`Failed to fetch document: ${JSON.stringify(result.errors)}`
);
}
return result.document;
}
// Mark document as read
async function markDocumentAsRead(teamId, documentId) {
const response = await fetch(
`https://peppol.recommand.eu/api/peppol/${teamId}/documents/${documentId}/markAsRead`,
{
method: "POST",
headers: {
Authorization:
"Basic " +
Buffer.from("your_api_key:your_api_secret").toString("base64"),
"Content-Type": "application/json",
},
body: JSON.stringify({ read: true }),
}
);
const result = await response.json();
if (!result.success) {
throw new Error(
`Failed to mark document as read: ${JSON.stringify(result.errors)}`
);
}
return result;
}
Downloading Document Packages
When processing documents received through webhooks, you may want to download the complete document package, including XML data and any attachments using the downloadPackage endpoint. This is particularly useful when debugging and testing. Document packages can also be downloaded manually from the dashboard.
// Download document package as a zip file
async function downloadDocumentPackage(teamId, documentId) {
const response = await fetch(
`https://peppol.recommand.eu/api/peppol/${teamId}/documents/${documentId}/downloadPackage`,
{
headers: {
Authorization:
"Basic " +
Buffer.from("your_api_key:your_api_secret").toString("base64"),
},
}
);
if (!response.ok) {
throw new Error(
`Failed to download document package: ${response.statusText}`
);
}
// Handle the zip file content
const packageData = await response.arrayBuffer();
// Example: Save the package to a file (Node.js)
const fs = require("fs");
fs.writeFileSync(`document-${documentId}.zip`, Buffer.from(packageData));
return packageData;
}
The downloaded zip file contains:
- The document JSON
- The document XML (UBL format)
- Any binary attachments referenced in the document (for invoices and credit notes)
This is particularly useful when you need to:
- Archive complete documents with all attachments
- Process attachments that were sent with the document
- Access the raw UBL XML for detailed processing (although this is also available in the document JSON)
Best Practices
- Respond quickly: Webhook handlers should acknowledge receipt promptly
- Process asynchronously: Handle complex processing outside the request/response cycle
- Implement retry logic: Be prepared to handle temporary failures
- Monitor webhook health: Keep track of successful and failed webhook deliveries
- Use idempotency: Process each event exactly once, even if received multiple times
- Handle all event types: Be prepared for new event types in the future
Complete Webhook Integration Example
// Setup authentication
const API_KEY = "your_api_key";
const API_SECRET = "your_api_secret";
const TEAM_ID = "your_team_id";
const AUTH =
"Basic " + Buffer.from(`${API_KEY}:${API_SECRET}`).toString("base64");
const BASE_URL = "https://peppol.recommand.eu/api/peppol";
// Create a webhook
async function setupWebhook() {
// 1. Create webhook
const createResponse = await fetch(`${BASE_URL}/${TEAM_ID}/webhooks`, {
method: "POST",
headers: {
Authorization: AUTH,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://your-domain.com/peppol-webhook?token=your_secret_token",
companyId: null, // For all companies
}),
});
const createResult = await createResponse.json();
if (!createResult.success) {
console.error("Failed to create webhook:", createResult.errors);
return null;
}
console.log(`Webhook created with ID: ${createResult.webhook.documentId}`);
// 2. Verify webhook was created
const listResponse = await fetch(`${BASE_URL}/${TEAM_ID}/webhooks`, {
headers: { Authorization: AUTH },
});
const listResult = await listResponse.json();
if (listResult.success) {
console.log("Active webhooks:");
listResult.webhooks.forEach((webhook) => {
console.log(`- ID: ${webhook.documentId}, URL: ${webhook.url}`);
});
}
return createResult.webhook.documentId;
}
// Example Express.js webhook handler
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());
app.post("/peppol-webhook", async (req, res) => {
// Verify token
const token = req.query.token;
if (token !== "your_secret_token") {
return res.status(403).send("Unauthorized");
}
const event = req.body;
console.log("Received webhook event:", event);
// Immediately acknowledge the webhook
res.status(200).send("Event received");
// Process the event asynchronously
try {
if (event.eventType === "document.received") {
// Get document details
const documentResponse = await fetch(
`${BASE_URL}/${event.teamId}/documents/${event.documentId}`,
{ headers: { Authorization: AUTH } }
);
const documentResult = await documentResponse.json();
if (documentResult.success) {
const document = documentResult.document;
// Process the document in your system
await processDocument(document);
// Mark as read
await fetch(
`${BASE_URL}/${event.teamId}/documents/${event.documentId}/markAsRead`,
{
method: "POST",
headers: {
Authorization: AUTH,
"Content-Type": "application/json",
},
body: JSON.stringify({ read: true }),
}
);
console.log(`Document ${event.documentId} processed successfully`);
} else {
console.error("Failed to fetch document:", documentResult.errors);
}
}
} catch (error) {
console.error("Error processing webhook event:", error);
}
});
// Process document function (implement based on your business logic)
async function processDocument(document) {
// Example: Extract document data and save to your database
const { id, direction, senderId, receiverId, type, parsed } = document;
console.log(
`Processing ${direction} ${type} from ${senderId} to ${receiverId}`
);
// Your business logic here...
// - Save to database
// - Create entry in accounting system
// - Notify users
// etc.
}
// Start the server
app.listen(3000, () => {
console.log("Webhook server listening on port 3000");
});
// Setup the webhook
setupWebhook().catch((error) => {
console.error("Error setting up webhook:", error);
});