# Koppelingen (inbound integratie-API) Externe systemen (webshop, kassa/POS, orderpakket) sturen data Facturix in via een aparte, machine-geauthenticeerde API onder `/api/v1/integration/`. Deze API staat los van de browser-API: hij gebruikt langlevende **API-sleutels** in plaats van sessies, is **idempotent** op het eigen kenmerk van het bronsysteem, en werkt altijd binnen één administratie. > Dit is de inbound-kant (data erin). Distributie naar buiten (post, e-mail, > betaalproviders) en de boekhoudkoppeling naar Ledgerix (trix-connect) zijn > aparte trajecten. ## Twee soorten koppelingen - **Kant-en-klare integraties** (pagina **Integraties**): Facturix haalt zelf gegevens op bij een bekend systeem. Je kiest een integratie uit de catalogus, vult de inloggegevens in, en Facturix synchroniseert periodiek of op verzoek. Beschikbaar: **WooCommerce** en **Shopify** (webshop-bestellingen worden facturen, idempotent op het bestelnummer), **Lightspeed** (kassaverkopen worden betaalde bonfacturen; beta) en **Mollie** (betaalde payments worden aan de bijbehorende factuur gekoppeld via het factuurnummer in de omschrijving/metadata). Inloggegevens worden versleuteld bewaard. - **Eigen koppeling via de API** (dit document): je eigen systeem duwt data naar Facturix met een API-sleutel. Gebruik dit als er geen kant-en-klare integratie is, of voor maatwerk. De rest van dit document beschrijft de API voor eigen koppelingen. ## Authenticatie Elke koppeling krijgt een eigen sleutel. Aanmaken en intrekken kan in **Instellingen → API-sleutels (koppelingen)**. De sleutel wordt één keer getoond bij aanmaken (formaat `fkx__`); bewaar hem veilig, hij is daarna niet meer op te vragen. Stuur de sleutel mee als Bearer-token: ``` Authorization: Bearer fkx_ab12cd..._9f8e... ``` Een sleutel hoort bij precies één administratie. De caller kan die scope niet verbreden: elk verzoek werkt op de administratie van de sleutel, ongeacht wat er in de body staat. Ingetrokken sleutels geven `401`. ## Idempotentie Geef bij elke aanroep het **eigen kenmerk** van het bronsysteem mee als `external_ref` (bijv. het webshop-bestelnummer of het kassabon-id). Facturix onthoudt de koppeling van dat kenmerk naar de aangemaakte klant/document, zodat een herhaalde webhook of een resend nooit dubbel aanmaakt. Dezelfde `external_ref` levert altijd hetzelfde record terug. ## Endpoints ### `POST /integration/relations` — klant opzoeken of aanmaken Idempotent op `external_ref`. Bestaat de klant al, dan wordt hij bijgewerkt. ```json { "external_ref": "shop-cust-42", "name": "Jansen BV", "email": "info@jansen.test", "street": "Dorpsstraat 1", "postal_code": "1234 AB", "city": "Amsterdam", "vat": "NL001234567B01", "iban": "NL00BANK0123456789", "segment": "zakelijk" } ``` Antwoord: `{ "id": "...", "external_ref": "shop-cust-42", "created": true }` ### `POST /integration/sales` — verkoopdocument aanmaken Maakt een factuur (default) of order aan, idempotent op `external_ref`. | Veld | Betekenis | |-----------------------|------------------------------------------------------------------| | `external_ref` | Verplicht. Eigen kenmerk van de bestelling/bon. | | `kind` | `"invoice"` (default) of `"order"`. | | `customer` | `{ relation_id }`, of `{ external_ref, name?, ... }`, of `{ name, ... }`. | | `lines[]` | `description`, `quantity`, `unit_price`, `vat`, optioneel `unit`, `discount_pct`, `line_type` (`item`/`text`). | | `prices_include_vat` | Prijzen incl. btw ingevoerd (default `false`). | | `reference` | Vrije referentie op het document. | | `issue_date` | `YYYY-MM-DD` (default vandaag). | | `finalize` | Definitief maken + nummer toekennen (default `true`). | | `paid` | Direct als betaald boeken (alleen facturen; een POS-bon). | | `email` | Na finaliseren e-mailen naar de klant (best-effort). | Webshop-bestelling (factuur, nog niet betaald, wel verstuurd): ```bash curl -X POST https://mijn-facturix/api/v1/integration/sales \ -H "Authorization: Bearer $FKX" -H "Content-Type: application/json" -d '{ "external_ref": "shop-order-1001", "customer": { "external_ref": "shop-cust-42" }, "reference": "Webshop #1001", "lines": [{ "description": "T-shirt L", "quantity": "2", "unit_price": "19.95", "vat": "standard" }] }' ``` Kassabon (afgerekend, dus direct betaald): ```bash curl -X POST https://mijn-facturix/api/v1/integration/sales \ -H "Authorization: Bearer $FKX" -H "Content-Type: application/json" -d '{ "external_ref": "pos-2026-000042", "customer": { "name": "Toonbankverkoop" }, "paid": true, "lines": [{ "description": "Koffie", "quantity": "3", "unit_price": "2.50", "vat": "reduced" }] }' ``` Antwoord (beide): ```json { "id": "…", "external_ref": "…", "kind": "invoice", "number": "2026-0001", "status": "paid", "total": "121.00", "paid": "121.00", "open": "0.00" } ``` ### `POST /integration/sales/{external_ref}/invoice` — order factureren Voor de webshop-keten: een eerder aangemaakte **order** omzetten naar een finale factuur, bijvoorbeeld op het moment van verzenden. Idempotent: dezelfde order twee keer factureren geeft dezelfde factuur (adresseerbaar als `#invoice`). Body (optioneel): `{ "paid": false, "email": false }`. ```bash curl -X POST https://mijn-facturix/api/v1/integration/sales/shop-order-1001/invoice \ -H "Authorization: Bearer $FKX" -H "Content-Type: application/json" -d '{ "paid": true }' ``` ### `GET /integration/sales/{external_ref}` — status opvragen Geeft hetzelfde statusobject terug voor een eerder aangemaakt document. ## Kassa (POS): verzamelen en dagafsluiting Voor een kassa die veel losse afrekeningen doet, kun je transacties per kassa (`register`) en dag verzamelen en aan het eind van de dag in één betaalde factuur afsluiten (Z-afsluiting). De tegenpartij is een vaste balie-klant per kassa (`Kassa `), automatisch aangemaakt. ### `POST /integration/pos/transactions` — transactie toevoegen Idempotent op `external_ref` (het bon-/transactie-id van de kassa). Prijzen mogen incl. btw (typisch aan de balie). ```json { "register": "kassa-1", "external_ref": "pos-2026-000042", "description": "Koffie", "quantity": "3", "unit_price": "2.50", "vat": "reduced", "business_day": "2026-09-10" } ``` ### `POST /integration/pos/close` — dagstaat afsluiten Bundelt de open transacties van `register` + `business_day` in één definitieve, betaalde factuur. Idempotent: een al afgesloten dag geeft dezelfde factuur terug. ```json { "register": "kassa-1", "business_day": "2026-09-10" } ``` ### `GET /integration/pos/batch?register=&business_day=` De status van een kassadag: `{ "closed": false, "open_count": 12, "open_total": "…" }` zolang open, of het factuur-statusobject zodra afgesloten. ## Webhooks (uitgaande gebeurtenissen) Stel in **Instellingen → Webhooks** een URL en een ondertekengeheim in. Facturix POST't dan een ondertekend JSON-bericht zodra: - een document **definitief** wordt (`invoice.finalized`), of - een factuur **volledig betaald** raakt (`invoice.paid`, ongeacht of dat via de UI, bankimport of de integratie-API gebeurt). ```json { "event": "invoice.finalized", "id": "…", "kind": "invoice", "number": "2026-0001", "status": "sent", "total": "121.00", "external_ref": "shop-order-1001", "occurred_at": "2026-09-10T12:00:00Z" } ``` De body is ondertekend met **HMAC-SHA256** over de exacte bytes, in de header: ``` X-Facturix-Signature: sha256= ``` Verifieer aan de ontvangkant met hetzelfde geheim (voorbeeld in Node): ```js const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex'); if (expected !== req.headers['x-facturix-signature']) reject(); ``` Aflevering is best-effort met een paar retries; een verzoek geldt als geslaagd bij een 2xx-status. Met de **Test**-knop stuur je een `ping` om de endpoint te controleren. ## Foutmeldingen Fouten komen als JSON met een `error_key`: | `error_key` | Betekenis | |------------------------------------------|---------------------------------------------| | `error.integration.unauthorized` | Ontbrekende, ongeldige of ingetrokken sleutel. | | `error.integration.external_ref_required`| `external_ref` ontbreekt. | | `error.integration.customer_required` | Geen klant opgegeven of vindbaar. | | `error.invoice.no_lines` | Geen regels in de verkoop. | | `error.pos.register_required` | Kassanaam (`register`) ontbreekt. | | `error.pos.nothing_to_close` | Geen open transacties om af te sluiten. | | `error.webhook.not_configured` | Test zonder ingestelde webhook-URL. | ## Aandachtspunten - **Btw-behandeling** (`vat`) per regel: `standard`, `reduced`, `zero`, `exempt`, `reverse`. Facturix rekent zelf het bedrag uit; stuur geen btw-bedrag mee. - **Bedragen** als decimale strings (`"19.95"`), niet als getal. - **Orders** tellen niet als omzet; pas na conversie naar een factuur wel. - Zet uitgeschakelde documentsoorten (Instellingen → Documentsoorten) weer aan als een koppeling ze nodig heeft.