Documentation

API Reference

Our comprehensive API reference documentation.

Visit API Reference
Back to documentation

Working with Webhooks

How-To Guides

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");
});
javascript

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';
php

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);
}
javascript

Webhook Fields

FieldDescriptionRequiredExample
urlYour webhook endpoint URLYes"https://your-domain.com/peppol-webhook"
companyIdSpecific 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}`);
});
javascript

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);
}
javascript

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);
}
javascript

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"
}
json

Upon receiving this event, you would typically:

  1. Check the eventType to ensure it is a document received event
  2. Retrieve the full document using the documentId via the get document endpoint
  3. Process the document in your system
  4. 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;
}
javascript

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;
}
javascript

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

  1. Respond quickly: Webhook handlers should acknowledge receipt promptly
  2. Process asynchronously: Handle complex processing outside the request/response cycle
  3. Implement retry logic: Be prepared to handle temporary failures
  4. Monitor webhook health: Keep track of successful and failed webhook deliveries
  5. Use idempotency: Process each event exactly once, even if received multiple times
  6. 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);
});
javascript

Next Steps