Developers
Technisch integratiedossier v1.2 · OpenAPI JSON
Registreer elke factuur vóór verzending. Plaats daarna de QR-code met verification_url op de PDF of in de e-mail.
POST /api/public/v1/invoices
curl -X POST https://<uw-domein>/api/public/v1/invoices \
-H "Authorization: Bearer igk_live_XXXXXXXXXXXXXXXX" \
-F "file=@factuur-2026-0042.pdf" \
-F 'metadata={
"invoice_number": "2026-0042",
"invoice_date": "2026-03-31",
"due_date": "2026-04-10",
"supplier_name": "TB Consultancy BV",
"supplier_vat": "BE0753659316",
"customer_name": "DocIT Consult BV",
"customer_vat": "BE0XXXXXXXXX",
"amount_excl_vat": 11000.00,
"vat_amount": 2310.00,
"amount": 13310.00,
"currency": "EUR",
"iban": "<goedgekeurd bedrijfs-IBAN>",
"bic": "KREDBEBB",
"structured_reference": "+++250/0369/88209+++"
}'Antwoord (201)
{
"duplicate": false,
"invoice_id": "4b1c…",
"sha256": "993a3401…",
"status": "VERIFIED",
"findings": [],
"layers": { "ibanFormat": "pass", "approvedSupplierIban": "pass", "canonicalInvoiceMatch": "pass", "vopNameMatch": "not_configured" },
"qr": { "status": "decoded", "count": 1, "payment": [{ "iban_masked": "BE80 •••• •••• 1377", "amount_cents": 1331000, "currency": "EUR", "reference": "+++250/0369/88209+++", "name": "TB Consultancy BV" }], "other": 0 },
"verification_token": "igv_…",
"verification_url": "https://<uw-domein>/v/igv_…"
}Statussen
- VERIFIED — alle controles ok.
- REVIEW — manuele controle aanbevolen.
- BLOCKED — onbekende of afwijkende IBAN, totaal, mededeling, valuta of betaal-QR; niet versturen/betalen zonder verificatie.
currency is verplicht (ISO 4217, wordt nooit verondersteld). Optioneel: invoice_date, due_date, customer_name, customer_vat, amount_excl_vat, vat_amount, bic, structured_reference. QR-codes worden server-side uit de PDF gelezen en als bewijs bewaard; QR-data in metadata wordt niet aanvaard.
Webhook-events (live)
- invoice.registration_accepted — canonieke factuur geregistreerd (status, invoice_id, sha256)
- invoice.registration_rejected — registratie geweigerd (foutcode, geen factuur aangemaakt)
- finding.created — kritieke afwijking bij een ontvangerscontrole (regels, ernst)
- verification.checked — ontvanger controleerde een PDF (resultaat VERIFIED/REVIEW/BLOCKED)
- verification_token.rotated — nieuwe verificatielink; oude QR werkt niet meer
- verification_token.revoked — verificatielink ingetrokken
Payload bevat nooit een volledig IBAN (alleen laatste 4). Header X-Signature: t=<unix>,v1=<hex> = HMAC-SHA256(geheim, t + "." + ruwe body); weiger ouder dan 5 min. Dedupliceer op X-Event-Id. Antwoord 2xx binnen 5 s; anders tot 8 retries (1 min → 6 u). Endpoints en geheim beheert een beheerder onder Dashboard → Integraties.
Terugkoppeling naar uw software
Twee kanalen met dezelfde events: push (ondertekende webhooks) en pull (events-API met cursor). Aanbevolen: webhooks voor snelheid, en periodiek /events?after= als vangnet zodat geen enkele melding gemist wordt. Toon “beschermd” in uw software alleen zolang protected: true; bij finding.created of verification_token.revoked het label intrekken.
POST <uw endpoint>
X-Event-Id: 6f2c… X-Event-Type: finding.created
X-Signature: t=1791459000,v1=9b1e…
{ "id": "6f2c…", "sequence": 412, "type": "finding.created",
"created_at": "2026-10-08T12:30:00Z", "invoice_id": "8f0c…",
"data": { "invoice_number": "2026036", "external_ref": "…", "severity": "BLOCKED",
"rules": ["IBAN_MISMATCH"], "found_iban_last4": ["2886"] } }GET /api/public/v1/events?after=411&limit=100 (scope invoices:read)
→ { "events": [ …envelopes… ], "next_after": 412, "has_more": false }
GET /api/public/v1/invoices/8f0c…
→ { "status": "VERIFIED", "protected": true, "sha256": "…", "iban_masked": "BE10 •••• •••• 6604",
"verification_link": { "active": true, "url": "…" }, "superseded_by": null,
"checks": { "total": 3, "blocked": 1, "review": 0 }, "open_findings": [ … ] }// Node / Workers – webhook-ontvanger
import crypto from "node:crypto";
function verify(secret, header, rawBody) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const exp = crypto.createHmac("sha256", secret).update(m[1] + "." + rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(exp), Buffer.from(m[2]));
}Integratie #1: MyDocIT
MyDocIT is gewoon een API-client. Billver heeft geen afhankelijkheid van de broncode van MyDocIT; andere systemen (Teamleader, Odoo, Exact, Billit) gebruiken dezelfde API.
Aanbevolen flow (twee stappen)
- Prepare: MyDocIT vraagt een verificatiereferentie aan → krijgt
verification_url+ QR. - MyDocIT rendert de definitieve PDF mét die QR.
- Finalize: MyDocIT stuurt exact die definitieve PDF + metadata. Billver hasht en bewaart die als canoniek origineel en activeert dan pas de link.
- MyDocIT verstuurt exact dezelfde bytes (e-mail/Peppol). Elke wijziging achteraf geeft een hashverschil.
Zo is er geen cirkel “hash, daarna PDF aanpassen”: de URL is vooraf bekend (afgeleid van het gereserveerde ID), maar de hash wordt pas berekend over de definitieve PDF. Bij finalize wordt gecontroleerd of de verificatie-QR in de PDF terug te vinden is (verification_qr_embedded: true / false / null = vector-QR niet uitleesbaar). Eén stap (direct POST /invoices) mag alleen als de QR niet in de PDF komt, bv. enkel in de e-mail.
POST /api/public/v1/invoices/prepare
Authorization: Bearer igk_live_… (alleen server-side bij MyDocIT)
Idempotency-Key: mydocit-<tenant>-<factuur-id>
X-Source: mydocit
201 {
"reservation_id": "8f0c…", // wordt het invoice_id
"verification_url": "https://<uw-domein>/v/igv_…",
"qr_svg": "<svg …>",
"expires_at": "…", // 7 dagen
"note": "Link is pas actief na finalize met de definitieve PDF."
}POST /api/public/v1/invoices
Authorization: Bearer igk_live_…
Idempotency-Key: mydocit-<tenant>-<factuur-id>
X-Source: mydocit
multipart: file=<DEFINITIEVE PDF mét QR>, reservation_id=8f0c…, metadata={…}
201 { "invoice_id": "8f0c…", "sha256": "…", "status": "VERIFIED",
"verification_url": "…", "verification_qr_embedded": true,
"qr_list": [ … ], "findings": [], "protected": true }
200 { "duplicate": true, "idempotent_replay": true, "invoice_id": "8f0c…", … } // retry
409 { "error": "idempotency_conflict", "protected": false } // andere PDF, zelfde sleutelIdempotentie
Header Idempotency-Key (of external_ref), uniek per tenant. Retry met dezelfde PDF → hetzelfde invoice_id (200). Zelfde sleutel met een andere PDF → 409, nooit een tweede canonieke factuur. Correcties: nieuwe sleutel → nieuwe revisie die de vorige vervangt.
Authenticatie
API-sleutel per tenant, gehasht opgeslagen, intrekbaar, met scopes (invoices:write). Alleen server-side bij MyDocIT bewaren; nooit in een browser, PDF of e-mail.
Fouten & fallback
Elk antwoord zonder 200/201 en status = niet beschermd. Bij time-out, 5xx, 503 of 409: géén QR/“beschermd”-label tonen, retry met dezelfde Idempotency-Key (exponentiële backoff), of verzenden zonder verificatiecue. Toon “beschermd” alleen bij protected: true.
Fouten: 401 ongeldige sleutel · 403 onvoldoende rechten · 422 ongeldige metadata · 409 idempotency_conflict · 429 rate limit · 503 verification_domain_not_configured. Elk foutantwoord bevat "protected": false. Hetzelfde bestand opnieuw registreren is idempotent.