### Complete Webhook Integration Example (JavaScript/Express.js) Source: https://docs.recommand.eu/docs/working-with-webhooks A full example that sets up authentication, creates a webhook via the API, verifies its creation, and provides an Express.js endpoint that verifies the HMAC signature, acknowledges the event, and processes it asynchronously. Includes a placeholder processDocument function and starts the server on port 3000. ```javascript // Setup authentication const API_KEY = "your_api_key"; const API_SECRET = "your_api_secret"; const AUTH = "Basic " + Buffer.from(`${API_KEY}:${API_SECRET}`).toString("base64"); const BASE_URL = "https://app.recommand.eu/api/v1"; const WEBHOOK_SECRET = process.env.RECOMMAND_WEBHOOK_SECRET; // Create a webhook async function setupWebhook() { // 1. Create webhook const createResponse = await fetch(`${BASE_URL}/webhooks`, { method: "POST", headers: { Authorization: AUTH, "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://your-domain.com/peppol-webhook", companyId: null, // For all companies secret: WEBHOOK_SECRET, }), }); 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}/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 crypto = require("node:crypto"); const express = require("express"); const app = express(); app.post("/peppol-webhook", express.raw({ type: "application/json" }), async ( req, res ) => { // Verify the signature before parsing the request body const received = Buffer.from(req.get("X-Signature") ?? ""); const expected = Buffer.from( `sha256=${crypto .createHmac("sha256", WEBHOOK_SECRET) .update(req.body) .digest("hex")}` ); if ( received.length !== expected.length || !crypto.timingSafeEqual(received, expected) ) { return res.status(401).send("Invalid signature"); } const event = JSON.parse(req.body.toString("utf8")); 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}/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}/documents/${event.documentId}/mark-as-read`, { 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); }); ``` -------------------------------- ### Complete Invoice Example Source: https://docs.recommand.eu/docs A complete invoice example with all supported fields, demonstrating the structure for sending a document via the API. ```APIDOC ## Complete Invoice Example ### Description A complete invoice example with all supported fields, demonstrating the structure for sending a document via the API. ### Request Example ```javascript const invoice = { invoiceNumber: "INV-2025-001", issueDate: "2024-05-15", dueDate: "2024-06-15", note: "Thank you for your business", buyerReference: "PO-2024-001", seller: { vatNumber: "BE0123456789", name: "Your Company", street: "Your Street 1", city: "Brussels", postalZone: "1000", country: "BE", }, buyer: { vatNumber: "BE0987654321", name: "Customer Company", street: "Customer Street 1", city: "Antwerp", postalZone: "2000", country: "BE", }, paymentMeans: [ { paymentMethod: "credit_transfer", reference: "INV-2025-001", iban: "BE1234567890", }, ], paymentTerms: { note: "Net 30", }, lines: [ { name: "Consulting Services", description: "Professional consulting services", sellersId: "CS-001", quantity: "10.00", unitCode: "HUR", // Hours netPriceAmount: "100.00", netAmount: "1000.00", vat: { category: "S", percentage: "21.00", }, }, ], attachments: [ { id: "ATT-001", mimeCode: "application/pdf", filename: "contract.pdf", embeddedDocument: "base64encodeddocument...", }, ], }; ``` ``` -------------------------------- ### Retrieve a company with the get company endpoint Source: https://docs.recommand.eu/docs/managing-companies Fetches company details from the get company endpoint using Basic authentication. The example usage passes two arguments to getCompany, but the function signature only accepts one; the second argument is ignored. Logs the company object on success or an error message on failure. ```javascript async function getCompany(companyId) { const response = await fetch( `https://app.recommand.eu/api/v1/companies/${companyId}`, { headers: { Authorization: "Basic " + Buffer.from("your_api_key:your_api_secret").toString("base64"), }, } ); return response.json(); } // Example usage const companyDetails = await getCompany("your_team_id", "company_id"); if (companyDetails.success) { console.log(companyDetails.company); } else { console.error("Failed to retrieve company:", companyDetails.errors); } ``` -------------------------------- ### Poll company verification status with get company endpoint Source: https://docs.recommand.eu/docs/company-verification Use the get company endpoint to poll the `isVerified` field on the company object. The function returns the JSON response, and the example logs the verification status. `isVerified` becomes `true` after a successful identity check and resets to `false` if the company's `vatNumber` or `enterpriseNumber` changes. ```javascript async function getCompany(companyId) { const response = await fetch( `https://app.recommand.eu/api/v1/companies/${companyId}`, { headers: { Authorization: "Basic " + Buffer.from("your_api_key:your_api_secret").toString("base64"), }, } ); return response.json(); } // Example usage const result = await getCompany("company_id"); if (result.success) { console.log("Verified:", result.company.isVerified); } ``` -------------------------------- ### Complete Company Management Workflow in JavaScript Source: https://docs.recommand.eu/docs/managing-companies A full example showing how to authenticate and perform CRUD operations on companies using the recommand.eu API. It includes functions to create, list, update, and delete companies, and a workflow that runs them in sequence. Requires an API key, API secret, and team ID. ```javascript // 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 baseUrl = "https://app.recommand.eu/api/v1"; // Create a new company async function createCompany() { const response = await fetch(`${baseUrl}/companies`, { method: "POST", headers: { Authorization: auth, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Test Company", address: "123 Test Street", postalCode: "1000", city: "Brussels", country: "BE", enterpriseNumber: "0123456789", vatNumber: "BE0123456789", }), }); const result = await response.json(); if (result.success) { console.log(`Company created with ID: ${result.company.id}`); return result.company.id; } else { console.error("Failed to create company:", result.errors); return null; } } // List all companies async function listCompanies() { const response = await fetch(`${baseUrl}/companies`, { headers: { Authorization: auth }, }); const result = await response.json(); if (result.success) { console.log("Companies:"); result.companies.forEach((company) => { console.log(`- ${company.name} (${company.id})`); }); return result.companies; } else { console.error("Failed to list companies:", result.errors); return []; } } // Update a company async function updateCompany(companyId) { const response = await fetch(`${baseUrl}/companies/${companyId}`, { method: "PUT", headers: { Authorization: auth, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Updated Test Company", address: "456 Updated Street", postalCode: "1000", city: "Brussels", country: "BE", enterpriseNumber: "0123456789", vatNumber: "BE0123456789", }), }); const result = await response.json(); if (result.success) { console.log("Company updated successfully"); return true; } else { console.error("Failed to update company:", result.errors); return false; } } // Delete a company async function deleteCompany(companyId) { const response = await fetch(`${baseUrl}/companies/${companyId}`, { method: "DELETE", headers: { Authorization: auth }, }); const result = await response.json(); if (result.success) { console.log("Company deleted successfully"); return true; } else { console.error("Failed to delete company:", result.errors); return false; } } // Example workflow async function manageCompanies() { // List existing companies console.log("Existing companies:"); await listCompanies(); // Create a new company console.log("\nCreating a new company..."); const companyId = await createCompany(); if (!companyId) return; // List companies again to see the new one console.log("\nUpdated company list:"); await listCompanies(); // Update the company console.log("\nUpdating the company..."); await updateCompany(companyId); // List companies again to see the update console.log("\nAfter update:"); await listCompanies(); // Delete the company console.log("\nDeleting the company..."); await deleteCompany(companyId); // Final company list console.log("\nFinal company list:"); await listCompanies(); } // Run the workflow manageCompanies().catch((error) => { console.error("Error in company management workflow:", error); }); ``` -------------------------------- ### Company registration JSON payload (company.json) Source: https://docs.recommand.eu/getting-started/other/business/sending Example payload for registering a company via the dashboard or the create company endpoint. The country must be an ISO 3166-1 alpha-2 code, and the vatNumber must start with the same country code; mismatches are rejected. Both numbers are validated against national formats and check digits where available. ```json { "name": "Exempel AB", "address": "Storgatan 1", "postalCode": "111 51", "city": "Stockholm", "country": "SE", "enterpriseNumber": "5567321707", "vatNumber": "SE556732170701" } ``` -------------------------------- ### Complete Invoice Example (JavaScript) Source: https://docs.recommand.eu/docs Complete invoice example with all supported fields, including seller, buyer, payment means, payment terms, lines, and attachments. Use this as a reference when constructing a full invoice payload for the send-document endpoint. ```javascript const invoice = { invoiceNumber: "INV-2025-001", issueDate: "2024-05-15", dueDate: "2024-06-15", note: "Thank you for your business", buyerReference: "PO-2024-001", seller: { vatNumber: "BE0123456789", name: "Your Company", street: "Your Street 1", city: "Brussels", postalZone: "1000", country: "BE", }, buyer: { vatNumber: "BE0987654321", name: "Customer Company", street: "Customer Street 1", city: "Antwerp", postalZone: "2000", country: "BE", }, paymentMeans: [ { paymentMethod: "credit_transfer", reference: "INV-2025-001", iban: "BE1234567890", }, ], paymentTerms: { note: "Net 30", }, lines: [ { name: "Consulting Services", description: "Professional consulting services", sellersId: "CS-001", quantity: "10.00", unitCode: "HUR", // Hours netPriceAmount: "100.00", netAmount: "1000.00", vat: { category: "S", percentage: "21.00", }, }, ], attachments: [ { id: "ATT-001", mimeCode: "application/pdf", filename: "contract.pdf", embeddedDocument: "base64encodeddocument...", }, ], }; ``` -------------------------------- ### HTTP 400 Validation Error Response Source: https://docs.recommand.eu/docs/troubleshooting-guide Example HTTP 400 response from the send document endpoint when validation fails. The document is not sent and the attempt does not count toward usage. The errors object is keyed by field; root always contains the general message and unmapped errors, and each message may start with a validation rule identifier (e.g., PEPPOL-EN16931-R020, BR-CO-09). ```json { "success": false, "errors": { "root": [ "Document validation failed. Please ensure your document complies with all requirements (e.g. EN16931, PEPPOL BIS 3.0, etc.).", "PEPPOL-EN16931-R020: Seller electronic address MUST be provided." ], "buyer.vatNumber": ["BR-CO-09: The Seller VAT identifier shall have a prefix in accordance with ISO code ISO 3166-1 alpha-2."] } } ``` -------------------------------- ### Line-level discounts and surcharges example Source: https://docs.recommand.eu/docs/discounts-and-surcharges Shows a complete invoice with line-level discounts and surcharges. Product A applies a 10.00 discount to 5 units at 40.00 each, resulting in 190.00 net; Product B applies a 5.00 surcharge to 2 units at 60.00 each, resulting in 125.00 net. Line-level entries inherit VAT from the line and do not include VAT information. ```json { "invoiceNumber": "INV-TEST-002", "issueDate": "2025-01-15", "buyer": { "vatNumber": "BE0123456789", "name": "Customer NV", "street": "Main Street 1", "city": "Brussels", "postalZone": "1000", "country": "BE" }, "paymentMeans": [{ "iban": "BE68539007547034" }], "lines": [ { "name": "Product A", "quantity": "5.00", "netPriceAmount": "40.00", "vat": { "percentage": "21.00" }, "discounts": [ { "reason": "Promotional discount", "amount": "10.00" } ] }, { "name": "Product B", "quantity": "2.00", "netPriceAmount": "60.00", "vat": { "percentage": "6.00" }, "surcharges": [ { "reason": "Special packaging", "amount": "5.00" } ] } ] } ``` -------------------------------- ### GET /api/v1/companies/{companyId} Source: https://docs.recommand.eu/docs/managing-companies Retrieves details about a specific company using the get company endpoint. ```APIDOC ## [GET] /api/v1/companies/{companyId} ### Description Retrieves details about a specific company using the get company endpoint. ### Method GET ### Endpoint https://app.recommand.eu/api/v1/companies/{companyId} ### Parameters #### Path Parameters - **companyId** (string) - Required - The unique identifier of the company to retrieve. ### Authentication Basic authentication with API key and API secret. ### Request Example No request body required. ### Response #### Success Response (200) - **success** (boolean) - Indicates if the request was successful. - **company** (object) - The company details. #### Error Response - **success** (boolean) - Indicates if the request failed. - **errors** (array) - List of error messages. #### Response Example { "success": true, "company": {} } ``` -------------------------------- ### Send a Peppol invoice with the Recommand API Source: https://docs.recommand.eu/docs/sending-invoices Complete example that prepares invoice data, verifies the recipient via the /verify endpoint, sends the invoice to /send, and handles the response. Requires a valid company ID, recipient Peppol ID, and API credentials. The verification step aborts if the recipient is not registered. ```javascript const companyId = "c_xxx"; const recipientPeppolId = "0208:0123456789"; const token = Buffer.from("your_api_key:your_api_secret").toString("base64"); async function sendPeppolInvoice() { // Step 1: Prepare invoice data const invoice = { invoiceNumber: "INV-2025-001", issueDate: "2024-05-15", dueDate: "2024-06-15", note: "Thank you for your business", buyerReference: "PO-2024-001", buyer: { vatNumber: "BE0123456789", name: "Recipient Company", street: "Recipient Street 1", city: "Brussels", postalZone: "1000", country: "BE", }, paymentMeans: [ { paymentMethod: "credit_transfer", reference: "INV-2025-001", iban: "BE1234567890", }, ], paymentTerms: { note: "Net 30", }, lines: [ { name: "Consulting Services", description: "Professional consulting services", sellersId: "SRV-001", quantity: "10.00", unitCode: "HUR", netPriceAmount: "100.00", vat: { category: "S", percentage: "21.00", }, }, { name: "Software License", quantity: "1.00", netPriceAmount: "500.00", vat: { percentage: "21.00", }, }, ], }; // Step 2: Verify the recipient const verification = await fetch( "https://app.recommand.eu/api/v1/verify", { method: "POST", headers: { Authorization: "Basic " + token, "Content-Type": "application/json", }, body: JSON.stringify({ peppolAddress: recipientPeppolId, }), } ); const verificationResult = await verification.json(); if (!verificationResult.isValid) { console.error("Recipient is not registered in the Peppol network"); return; } // Step 3: Send the invoice const response = await fetch( `https://app.recommand.eu/api/v1/${companyId}/send`, { method: "POST", headers: { Authorization: "Basic " + token, "Content-Type": "application/json", }, body: JSON.stringify({ recipient: recipientPeppolId, documentType: "invoice", document: invoice, }), } ); // Step 4: Handle the response const result = await response.json(); if (result.success) { console.log("Invoice sent successfully!"); } else { console.error("Failed to send invoice:", result.errors); } } await sendPeppolInvoice(); ``` -------------------------------- ### Authentication Header Formatting Source: https://docs.recommand.eu/docs/troubleshooting-guide Demonstrates the correct and incorrect ways to format the Basic Authentication header in JavaScript. ```javascript // Correct format const auth = "Basic " + Buffer.from("key_xxx:secret_xxx").toString("base64"); // Incorrect format (missing 'Basic ' prefix) const auth = Buffer.from("key_xxx:secret_xxx").toString("base64"); ``` -------------------------------- ### GET /api/v1/documents/{documentId} Source: https://docs.recommand.eu/docs/receiving-documents Code snippet demonstrating how to fetch a document using the get document endpoint. ```APIDOC ## GET /api/v1/documents/{documentId} ### Description Retrieves the full parsed document for a given document ID. The inbox and webhook payloads provide a document ID but not the full parsed document, so this endpoint is used to access the complete document content. ### Method GET ### Endpoint `https://app.recommand.eu/api/v1/documents/{documentId}` ### Parameters #### Path Parameters - **documentId** (string) - Required - The unique identifier of the document to retrieve. ### Authentication Basic authentication with API key and API secret. ### Request Example ``` GET /api/v1/documents/doc_xxx HTTP/1.1 Host: app.recommand.eu Authorization: Basic base64(api_key:api_secret) ``` ### Response #### Success Response (200) - **success** (boolean) - Indicates whether the request was successful. - **document** (object) - The full document object containing the following fields: - **id** (string) - Unique document ID. - **companyId** (string) - The company that received the document. - **direction** (string) - Always `"incoming"` for received documents. - **type** (string) - Document type: `invoice`, `creditNote`, `selfBillingInvoice`, `selfBillingCreditNote`, `messageLevelResponse`, `frenchInvoicingCdar`, `frenchB2CSalesReport`, `frenchB2CPaymentReport`, `frenchB2BiInvoiceReport`, `frenchB2BiPaymentReport`, or `unknown`. - **senderId** (string) - Peppol address of the sender. - **receiverId** (string) - Peppol address of the receiver (your company). - **parsed** (object) - The structured document content (invoice, credit note, etc.). - **xml** (string) - The raw UBL XML. - **validation** (object|null) - Validation results against EN16931 and Peppol BIS 3.0, or `null` when the document was not validated. - **labels** (array) - Labels assigned to this document. #### Response Example ```json { "success": true, "document": { "id": "doc_xxx", "companyId": "c_xxx", "direction": "incoming", "type": "invoice", "senderId": "0208:0123456789", "receiverId": "0208:9876543210", "parsed": {}, "xml": "