Katsjing API

v1.0.0

Create and read invoices and relations, and subscribe to invoice events.

OpenAPI 3.1 specification (JSON) — import it into Postman, Insomnia or a client generator.

Introduction

This API lets a webshop, an automation tool or your own software create and read invoices and customers in a Katsjing administration, and be notified when an invoice is sent or paid. It is what WooCommerce, Shopify and Zapier integrations are built on.

Every request goes through exactly the same services the app itself uses. An invoice created here is numbered, booked into the ledger, given a payment QR code and — for business customers — an e-invoice, precisely as if someone had typed it in. There is no separate API-only path that could drift from the real one.

Base URL: https://www.katsjing.nl/api/v1. The machine-readable specification lives at https://www.katsjing.nl/api/v1/openapi.json (OpenAPI 3.1) — import it into Postman, Insomnia or a client generator.

Field names are Dutch, because they are the same names the product uses: factuurdatum, regels, omschrijving, openstaandBedrag. Error codes and messages are English. Every field is documented below, so you do not need to speak Dutch to use this — but do not guess at names.

Quickstart

  • In the app, go to Settings → API & webhooks and create an API key. It is shown once.
  • Create the customer (or reuse an id you stored earlier).
  • Create the invoice, either as a draft or finalised straight away.
1. Create a customer
curl -X POST https://www.katsjing.nl/api/v1/relaties \
  -H "Authorization: Bearer ksj_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "naam": "Jansen Interieur BV",
    "factuurEmail": "facturen@jansen-interieur.nl",
    "land": "NL"
  }'
2. Create and finalise an invoice
curl -X POST https://www.katsjing.nl/api/v1/facturen \
  -H "Authorization: Bearer ksj_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "relatieId": "<id from step 1>",
    "referentie": "order-8471",
    "definitiefMaken": true,
    "regels": [
      {
        "omschrijving": "Order #8471 — 2x wandlamp Nova",
        "aantal": "2",
        "prijsPerStukExclBtw": "50.00",
        "btwCode": "HOOG_21"
      }
    ]
  }'

The response carries the invoice number, the computed totals and the lines. Nothing is e-mailed: sending stays a deliberate act in the app, or your own order confirmation covers it.

Authentication

Send the API key as a Bearer token on every request. There is no other scheme, and no key in a query parameter — that would end up in access logs.

http
Authorization: Bearer ksj_0Vb3n1QeXk…

A key belongs to one administration and that scope is fixed. No endpoint takes an administration id, not in a URL and not in a body — so a key can never be pointed at someone else's books, however the request is shaped.

  • The full key is visible exactly once, when you create it. We store only a SHA-256 hash plus a short prefix for recognition. Lost it? Revoke and issue a new one.
  • Treat it like a password: server-side only. A key in browser JavaScript or a mobile app is a key you have published.
  • Revoking takes effect immediately, on the next request.
  • Keys cannot create keys. That would be an escalation path, so key management lives in the app only.

Conventions

TypeFormatExample
AmountString, two decimals, dot separator"121.00"
DateYYYY-MM-DD"2026-08-14"
TimestampISO-8601 in UTC"2026-08-14T10:00:00.000Z"
IdUUID v4"0f7a5d2c-9b1e-4a63-8f5b-2d3c4e5f6a7b"
Amounts are strings, never JSON numbers. Binary floating point cannot represent 0.10 exactly, and a cent that goes missing in a ledger is a cent nobody can find back. On input we accept a number as well, but we parse it as a decimal string — and we return strings.

Invoice totals are always computed from the lines, rounded half-up per VAT rate group. Do not send a total: if it disagreed with the lines, one of the two would have to lose, and neither answer is right.

Paging

List endpoints return { "data": [ … ], "volgendeCursor": "…" }. Pass that cursor back as cursor to get the next page; when it is null you have reached the end. Page size is limiet (1–100, default 25).

bash
curl "https://www.katsjing.nl/api/v1/facturen?status=VERZONDEN&limiet=100" \
  -H "Authorization: Bearer ksj_your_key_here"

# next page
curl "https://www.katsjing.nl/api/v1/facturen?status=VERZONDEN&limiet=100&cursor=<volgendeCursor>" \
  -H "Authorization: Bearer ksj_your_key_here"

Cursors, not offsets: rows created while you page would shift an offset window and make you skip or repeat records.

Errors

Every error has the same shape, whatever the status code:

json
{
  "fout": {
    "code": "VALIDATION_FAILED",
    "melding": "The request body is invalid.",
    "details": { "fieldErrors": { "regels": ["Array must contain at least 1 element(s)"] } }
  }
}
Branch on code, never on melding. Codes are an external contract and are never renamed; messages are prose and may be reworded.
CodeHTTPWhen
UNAUTHENTICATED401No Authorization header, or not a Bearer token.
INVALID_API_KEY401The key is unknown or has been revoked. Issue a new one in the app.
ADMINISTRATION_PENDING_DELETION403The administration is in its 30-day deletion window. Everything stays closed until the owner cancels the deletion.
RATE_LIMITED429More than 240 requests in a minute for this key. The message states when to retry.
VALIDATION_FAILED422The request body or a query parameter is invalid. details carries the field errors.
NOT_FOUND404No such resource in this administration. A resource belonging to someone else is indistinguishable from one that does not exist — that is deliberate.
PLAN_LIMIT_REACHED403The monthly invoice limit of the current subscription has been reached. Drafts are unaffected; only finalising is blocked.
UPGRADE_REQUIRED403The current subscription does not include this action.
WEBHOOKS_UNAVAILABLE503This environment has no encryption key configured, so no signing secret can be stored. We never fall back to plain-text storage.
CONFLICT409A limit on the resource itself, e.g. more than 20 webhook subscriptions.
INTERNAL_ERROR500Something went wrong on our side. Safe to retry a read; do not blind-retry a create.

Limits

  • Rate limit: 240 requests per minute per API key. Over it, you get RATE_LIMITED (429) with the retry delay in the message.
  • Plan limit: finalising an invoice counts towards the monthly limit of the administration's subscription. Creating drafts does not — so a batch import keeps working and only the finalising waits for an upgrade.
  • Webhooks: at most 20 subscriptions per administration.

Rate limiting is currently per server instance and best-effort. Do not build a burst strategy on the exact ceiling; back off on 429 and you will be fine.

Webhooks

Subscribe an https endpoint and we POST to it when something happens. Available events: factuur.verzonden and factuur.betaald. Event codes are an external contract and are never renamed — new ones get added, old ones stay.

factuur.betaald fires on the transition to fully paid, from whichever route the money arrived: entered by hand, matched from a bank statement, paid online in the customer portal, or collected by direct debit. It does not fire per partial payment.

The body we POST
{
  "id": "b4f0…",
  "gebeurtenis": "factuur.betaald",
  "tijdstip": "2026-08-14T10:00:00.000Z",
  "administratieId": "…",
  "data": {
    "factuur": {
      "id": "…",
      "factuurnummer": "2026-0042",
      "status": "BETAALD",
      "totaalInclBtw": "121.00",
      "openstaandBedrag": "0.00",
      "relatie": { "id": "…", "naam": "Jansen Interieur BV" }
    }
  }
}

data.factuur is the exact same shape as the invoice summary from the API, taken as a snapshot at the moment of the event. A retry never recomputes it.

HeaderMeaning
Katsjing-Webhook-IdDelivery id — the same value as id in the body.
Katsjing-Webhook-GebeurtenisThe event code, so you can route without parsing.
Katsjing-Handtekeningt=<unix seconds>,v1=<hex> — see below.
Delivery is at-least-once. Answer 2xx within 10 seconds; anything else (timeout, 4xx, 5xx) counts as failed and is retried after 1, 5, 15, 60, 360 and 1440 minutes — six attempts, then it is marked failed for good. Deduplicate on the delivery id, and do your slow work after answering.

Verifying the signature

Your endpoint is on the public internet, so anyone can POST to it. The signature is what tells you a delivery is ours: v1 is an HMAC-SHA256 over <t>.<raw body> using your signing secret, and t is the unix timestamp of the attempt.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

// `rauweBody` MUST be the raw request body as a string — not a re-serialised
// object. JSON.stringify(JSON.parse(body)) reorders keys and changes whitespace,
// and then the signature will never match.
export function verifieer(rauweBody, header, geheim) {
  const velden = Object.fromEntries(
    header.split(",").map((deel) => deel.split("=", 2)),
  );
  const t = Number(velden.t);
  if (!Number.isFinite(t)) return false;

  // Reject anything older than five minutes. The timestamp is inside the HMAC,
  // so stretching it breaks the signature — this only guards replays.
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;

  const verwacht = createHmac("sha256", geheim)
    .update(`${velden.t}.${rauweBody}`)
    .digest("hex");

  const a = Buffer.from(verwacht, "hex");
  const b = Buffer.from(velden.v1 ?? "", "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
PHP (WooCommerce)
<?php
// $rauweBody is file_get_contents('php://input') — the raw body, unparsed.
function katsjing_verifieer(string $rauweBody, string $header, string $geheim): bool {
    $velden = [];
    foreach (explode(',', $header) as $deel) {
        [$k, $v] = array_pad(explode('=', $deel, 2), 2, '');
        $velden[$k] = $v;
    }
    if (!isset($velden['t'], $velden['v1'])) return false;
    if (abs(time() - (int) $velden['t']) > 300) return false;

    $verwacht = hash_hmac('sha256', $velden['t'] . '.' . $rauweBody, $geheim);
    return hash_equals($verwacht, $velden['v1']);
}
  • Verify against the raw body. Parsing and re-serialising reorders keys and changes whitespace, and the HMAC will never match.
  • Compare in constant time (timingSafeEqual, hash_equals) — a plain === leaks how much of the signature you got right.
  • Reject deliveries older than about five minutes. The timestamp is inside the HMAC, so it cannot be stretched; the age check is what stops a captured request being replayed later.
  • The secret is shown once, when you create the subscription. We store it encrypted and cannot show it again — lost it, create a new subscription.

Zapier and no-code tools

Webhook subscriptions are manageable through the API itself (POST and DELETE /api/v1/webhooks), which is the REST hooks pattern these tools expect: the tool subscribes when a Zap is switched on and unsubscribes when it is switched off. Point it at the OpenAPI document above and it has everything it needs.

Invoices

Create and read sales invoices.

get/api/v1/facturen

List invoices

Newest invoice date first. Use cursor for paging; do not compute offsets yourself.

Query parameters

  • statusstring

    Only invoices with this status.

    CONCEPTVERZONDENGEDEELTELIJK_BETAALDBETAALDVERVALLENONINBAARGECREDITEERD

  • relatieIdstring (uuid)

    Only invoices for this customer.

  • vanafstring (date)

    Invoice date on or after this date.

  • totstring (date)

    Invoice date on or before this date.

  • limietinteger

    Page size, 1–100.

    min 1 · max 100 · default 25

  • cursorstring (uuid)

    The volgendeCursor value from the previous page.

Responses

  • 200FactuurLijstA page of invoices.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 422FoutAn unknown status, a malformed date or a malformed cursor.
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
Example
curl "https://www.katsjing.nl/api/v1/facturen?status=CONCEPT&relatieId=<relatieId>" \
  -H "Authorization: Bearer ksj_your_key_here"
post/api/v1/facturen

Create an invoice

Creates a draft by default. With definitiefMaken: true the invoice is numbered, booked into the ledger, given an EPC payment QR code and — for business customers — an e-invoice (UBL/NLCIUS), through exactly the same services the app itself uses.

Nothing is e-mailed from the API. Sending stays a deliberate act in the app (or your own order confirmation); the factuur.verzonden webhook tells you when the invoice mail has gone out.

Totals are always computed from the lines, rounded half-up per VAT rate group. Do not send a total.

Request body

  • relatieIdstring (uuid)required

    pattern ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$

  • handelsnaamIdstring (uuid)

    pattern ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$

  • factuurdatumstring

    pattern ^\d{4}-\d{2}-\d{2}$

  • betaaltermijnDageninteger

    min 0 · max 365

  • referentiestring

    max length 200

  • opmerkingstring

    max length 2000

  • definitiefMakenboolean

    default false

  • regelsarray of objectrequired

    min items 1 · max items 100

  • omschrijvingstringrequired

    min length 1 · max length 500

  • aantalstring | numberrequired
  • prijsPerStukExclBtwstring | numberrequired
  • btwCodestring

    HOOG_21LAAG_9VRIJGESTELDVERLEGDNUL_0

  • btwTariefIdstring (uuid)

    pattern ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$

Responses

  • 201FactuurThe created invoice, including its lines.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe plan's monthly invoice limit has been reached (PLAN_LIMIT_REACHED), the plan does not include this (UPGRADE_REQUIRED), or the administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 422FoutInvalid body, an unknown relatieId, a VAT code without an active rate in this administration, or an invoice date inside a closed financial year.
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
Example
curl -X POST https://www.katsjing.nl/api/v1/facturen \
  -H "Authorization: Bearer ksj_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "relatieId": "0f7a5d2c-9b1e-4a63-8f5b-2d3c4e5f6a7b",
  "referentie": "order-8471",
  "definitiefMaken": true,
  "regels": [
    {
      "omschrijving": "Order #8471 — 2× wandlamp Nova",
      "aantal": "2",
      "prijsPerStukExclBtw": "50.00",
      "btwCode": "HOOG_21"
    }
  ]
}'
get/api/v1/facturen/{factuurId}

Get an invoice

Path parameters

  • factuurIdstring (uuid)required

    Invoice id.

Responses

  • 200FactuurThe invoice, including its lines.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 404FoutNOT_FOUND — no invoice with this id in this administration. An invoice belonging to another administration is indistinguishable from one that does not exist.
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
Example
curl "https://www.katsjing.nl/api/v1/facturen/<factuurId>" \
  -H "Authorization: Bearer ksj_your_key_here"

Relations

Create and read customers and suppliers.

get/api/v1/relaties

List relations

Newest first.

Query parameters

  • isKlantboolean

    Only customers (true) or only non-customers (false).

  • isLeverancierboolean

    Only suppliers (true) or only non-suppliers (false).

  • zoekstring

    Case-insensitive substring match on the name.

  • limietinteger

    Page size, 1–100.

    min 1 · max 100 · default 25

  • cursorstring (uuid)

    The volgendeCursor value from the previous page.

Responses

  • 200RelatieLijstA page of relations.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 422FoutA malformed cursor.
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
Example
curl "https://www.katsjing.nl/api/v1/relaties?isKlant=true&isLeverancier=true" \
  -H "Authorization: Bearer ksj_your_key_here"
post/api/v1/relaties

Create a relation

The webshop path: create the customer, then the invoice. There is deliberately no find-or-create on e-mail address — silently matching would attach invoices to the wrong customer. Store the returned id on your side.

Request body

  • naamstringrequired

    min length 1 · max length 200

  • soortstring

    ZAKELIJKCONSUMENT

    default "ZAKELIJK"

  • isKlantboolean

    default true

  • isLeverancierboolean

    default false

  • contactpersoonstring

    max length 200

  • contactpersoonEmailstring (email)

    max length 320 · pattern ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$

  • factuurEmailstring (email)

    max length 320 · pattern ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$

  • telefoonstring

    max length 50

  • straatstring

    max length 200

  • huisnummerstring

    max length 20

  • postcodestring

    max length 20

  • plaatsstring

    max length 100

  • landstring

    min length 2 · max length 2 · default "NL"

  • ibanstring

    max length 34

  • btwNummerstring

    max length 20

  • kvkNummerstring

    max length 12

  • standaardBetaaltermijnDageninteger

    min 0 · max 365

Responses

  • 201RelatieThe created relation.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 422FoutInvalid body.
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
Example
curl -X POST https://www.katsjing.nl/api/v1/relaties \
  -H "Authorization: Bearer ksj_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "naam": "Jansen Interieur BV",
  "soort": "ZAKELIJK",
  "factuurEmail": "facturen@jansen-interieur.nl",
  "straat": "Keizersgracht",
  "huisnummer": "42",
  "postcode": "1015 CS",
  "plaats": "Amsterdam",
  "land": "NL"
}'
get/api/v1/relaties/{relatieId}

Get a relation

Path parameters

  • relatieIdstring (uuid)required

    Relation id.

Responses

  • 200RelatieThe relation.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 404FoutNOT_FOUND — no relation with this id in this administration.
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
Example
curl "https://www.katsjing.nl/api/v1/relaties/<relatieId>" \
  -H "Authorization: Bearer ksj_your_key_here"

Webhooks

Manage webhook subscriptions. This is the REST hooks pattern Zapier expects: subscribe with POST, unsubscribe with DELETE.

get/api/v1/webhooks

List webhook subscriptions

Responses

  • 200WebhookLijstAll subscriptions, without their secrets.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
Example
curl "https://www.katsjing.nl/api/v1/webhooks" \
  -H "Authorization: Bearer ksj_your_key_here"
post/api/v1/webhooks

Subscribe to events

The response carries geheim exactly once — that is the signing secret you verify deliveries with. The endpoint must be https and may not point at a loopback, private or link-local address.

Request body

  • urlstringrequired

    max length 500

  • gebeurtenissenarray of stringrequired

    factuur.verzondenfactuur.betaald

    min items 1

  • omschrijvingstring

    max length 200

Responses

  • 201WebhookAbonnementMetGeheimThe created subscription, including the signing secret.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 409FoutAt most 20 subscriptions per administration (CONFLICT).
  • 422FoutInvalid body, or a URL that fails the https/private-address check.
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
  • 503FoutWebhooks are unavailable on this environment because the encryption key is not configured (WEBHOOKS_UNAVAILABLE). We never store a signing secret in plain text.
Example
curl -X POST https://www.katsjing.nl/api/v1/webhooks \
  -H "Authorization: Bearer ksj_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://shop.example.com/hooks/katsjing",
  "gebeurtenissen": [
    "factuur.betaald"
  ],
  "omschrijving": "Order fulfilment"
}'
get/api/v1/webhooks/{webhookId}

Get a webhook subscription

Path parameters

  • webhookIdstring (uuid)required

    Subscription id.

Responses

  • 200WebhookAbonnementThe subscription.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 404FoutNOT_FOUND — no subscription with this id in this administration.
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
Example
curl "https://www.katsjing.nl/api/v1/webhooks/<webhookId>" \
  -H "Authorization: Bearer ksj_your_key_here"
delete/api/v1/webhooks/{webhookId}

Unsubscribe

The REST hooks unsubscribe path — Zapier calls this when a Zap is turned off. Pending deliveries for this subscription are removed with it.

Path parameters

  • webhookIdstring (uuid)required

    Subscription id.

Responses

  • 200The subscription was removed.
  • 401FoutMissing key (UNAUTHENTICATED) or unknown/revoked key (INVALID_API_KEY).
  • 403FoutThe administration is scheduled for deletion (ADMINISTRATION_PENDING_DELETION).
  • 404FoutNOT_FOUND — no subscription with this id in this administration.
  • 429FoutRate limit exceeded (RATE_LIMITED): more than 240 requests per minute for this key.
  • 500FoutUnexpected server error (INTERNAL_ERROR).
Example
curl -X DELETE https://www.katsjing.nl/api/v1/webhooks/<webhookId> \
  -H "Authorization: Bearer ksj_your_key_here"

Webhook events

These are requests we send to your endpoint.

postfactuur.verzonden

Invoice e-mail sent

Sent every time an invoice e-mail goes out successfully, including a second send of the same invoice.

200 Answer 2xx within 10 seconds. Anything else — a timeout, a 4xx or a 5xx — counts as failed and is retried with backoff 1, 5, 15, 60, 360 and 1440 minutes (6 attempts total).

postfactuur.betaald

Invoice fully paid

Sent on the transition to fully paid, via any route: a payment entered by hand, a matched bank transaction, an online payment in the customer portal, or a direct debit collection. It fires on the transition, not on every payment.

200 Answer 2xx within 10 seconds. Anything else — a timeout, a 4xx or a 5xx — counts as failed and is retried with backoff 1, 5, 15, 60, 360 and 1440 minutes (6 attempts total).

Schemas

Fout

The single error shape of this API.

  • foutobjectrequired
  • codestringrequired

    Machine-readable error code. This is an external contract: existing codes are never renamed. Branch on this, not on the message.

  • meldingstringrequired

    Human-readable explanation, in English, aimed at the developer.

  • detailsobject

    Present on VALIDATION_FAILED: the flattened field errors of the request body.

FactuurSamenvatting

An invoice without its lines. This is the exact shape used in webhook payloads, so what a consumer receives is what the GET returns.

  • idstring (uuid)required

    Invoice id.

  • factuurnummerstring | nullrequired

    The issued invoice number. null while the invoice is still a draft — a draft has no number yet, and the internal placeholder is never exposed.

  • statusstringrequired

    Invoice status.

    CONCEPTVERZONDENGEDEELTELIJK_BETAALDBETAALDVERVALLENONINBAARGECREDITEERD

  • isCreditnotabooleanrequired

    True for a credit note. Credit notes carry negative amounts.

  • factuurdatumstring (date)required

    Invoice date; drives the financial year the entry is booked in.

  • vervaldatumstring (date)required

    Due date, derived from the invoice date and the payment term.

  • betaaltermijnDagenintegerrequired

    Payment term in days.

  • referentiestring | nullrequired

    Your own reference, e.g. a webshop order number.

  • valutastringrequired

    ISO 4217 currency code. Always EUR; the bookkeeping is euro-only.

  • subtotaalExclBtwstringrequired

    Total excluding VAT.

    pattern ^-?\d+\.\d{2}$

  • totaalBtwstringrequired

    VAT total, rounded half-up per VAT rate group.

    pattern ^-?\d+\.\d{2}$

  • totaalInclBtwstringrequired

    Total including VAT.

    pattern ^-?\d+\.\d{2}$

  • betaaldBedragstringrequired

    Amount received so far.

    pattern ^-?\d+\.\d{2}$

  • openstaandBedragstringrequired

    Amount still outstanding: total plus collection costs and interest, minus payments and any written-off amount, floored at zero.

    pattern ^-?\d+\.\d{2}$

  • definitiefOpstring (date-time) | nullrequired

    When the invoice was finalised: numbered, booked and irreversible. null for drafts.

  • verzondenOpstring (date-time) | nullrequired

    When the invoice e-mail was first sent successfully. null means it has never been e-mailed — a finalised invoice can be booked without ever being sent.

  • betaaldOpstring (date-time) | nullrequired

    When the invoice became fully paid.

  • relatieobjectrequired

    The customer this invoice was issued to.

  • idstringrequired

    Relation id.

  • naamstringrequired

    Relation name.

  • aangemaaktOpstring (date-time)required

    Creation timestamp.

  • bijgewerktOpstring (date-time)required

    Last modification timestamp.

FactuurRegel

A single invoice line.

  • idstring (uuid)required

    Line id.

  • volgordeintegerrequired

    Position of the line on the invoice, ascending.

  • omschrijvingstringrequired

    Line description as printed on the invoice.

  • aantalstringrequired

    Quantity.

    pattern ^-?\d+\.\d{2}$

  • prijsPerStukExclBtwstringrequired

    Unit price excluding VAT.

    pattern ^-?\d+\.\d{2}$

  • btwPercentagestringrequired

    VAT percentage applied to this line, e.g. 21.00.

    pattern ^-?\d+\.\d{2}$

  • btwTariefIdstring (uuid) | nullrequired

    Id of the VAT rate used, within this administration.

  • regelExclBtwstringrequired

    Line total excluding VAT.

    pattern ^-?\d+\.\d{2}$

  • regelBtwstringrequired

    VAT on this line.

    pattern ^-?\d+\.\d{2}$

  • regelInclBtwstringrequired

    Line total including VAT.

    pattern ^-?\d+\.\d{2}$

Factuur

An invoice including its lines.

  • idstring (uuid)required

    Invoice id.

  • factuurnummerstring | nullrequired

    The issued invoice number. null while the invoice is still a draft — a draft has no number yet, and the internal placeholder is never exposed.

  • statusstringrequired

    Invoice status.

    CONCEPTVERZONDENGEDEELTELIJK_BETAALDBETAALDVERVALLENONINBAARGECREDITEERD

  • isCreditnotabooleanrequired

    True for a credit note. Credit notes carry negative amounts.

  • factuurdatumstring (date)required

    Invoice date; drives the financial year the entry is booked in.

  • vervaldatumstring (date)required

    Due date, derived from the invoice date and the payment term.

  • betaaltermijnDagenintegerrequired

    Payment term in days.

  • referentiestring | nullrequired

    Your own reference, e.g. a webshop order number.

  • valutastringrequired

    ISO 4217 currency code. Always EUR; the bookkeeping is euro-only.

  • subtotaalExclBtwstringrequired

    Total excluding VAT.

    pattern ^-?\d+\.\d{2}$

  • totaalBtwstringrequired

    VAT total, rounded half-up per VAT rate group.

    pattern ^-?\d+\.\d{2}$

  • totaalInclBtwstringrequired

    Total including VAT.

    pattern ^-?\d+\.\d{2}$

  • betaaldBedragstringrequired

    Amount received so far.

    pattern ^-?\d+\.\d{2}$

  • openstaandBedragstringrequired

    Amount still outstanding: total plus collection costs and interest, minus payments and any written-off amount, floored at zero.

    pattern ^-?\d+\.\d{2}$

  • definitiefOpstring (date-time) | nullrequired

    When the invoice was finalised: numbered, booked and irreversible. null for drafts.

  • verzondenOpstring (date-time) | nullrequired

    When the invoice e-mail was first sent successfully. null means it has never been e-mailed — a finalised invoice can be booked without ever being sent.

  • betaaldOpstring (date-time) | nullrequired

    When the invoice became fully paid.

  • relatieobjectrequired

    The customer this invoice was issued to.

  • idstringrequired

    Relation id.

  • naamstringrequired

    Relation name.

  • aangemaaktOpstring (date-time)required

    Creation timestamp.

  • bijgewerktOpstring (date-time)required

    Last modification timestamp.

  • regelsarray of FactuurRegelrequired

    The invoice lines, in printing order.

FactuurLijst

A page of invoices.

  • dataarray of FactuurSamenvattingrequired
  • volgendeCursorstring (uuid) | nullrequired

    Pass as cursor to fetch the next page. null means this was the last page.

Relatie

A customer or supplier.

  • idstring (uuid)required

    Relation id.

  • soortstringrequired

    Business or consumer. This drives VAT treatment and whether an e-invoice (UBL) is generated: consumers never get one.

    ZAKELIJKCONSUMENT

  • isKlantbooleanrequired

    Can be invoiced (appears in accounts receivable).

  • isLeverancierbooleanrequired

    Can send us purchase invoices (appears in accounts payable).

  • naamstringrequired

    Relation name.

  • contactpersoonstring | nullrequired

    Contact person.

  • contactpersoonEmailstring | nullrequired

    Personal address of the contact person. Never used for document delivery — see factuurEmail.

  • factuurEmailstring | nullrequired

    The address every outgoing document goes to: invoices, quotes, reminders, dunning letters and the customer-portal link. There is deliberately no fallback to contactpersoonEmail.

  • telefoonstring | nullrequired

    Phone number.

  • straatstring | nullrequired

    Street.

  • huisnummerstring | nullrequired

    House number.

  • postcodestring | nullrequired

    Postal code.

  • plaatsstring | nullrequired

    City.

  • landstringrequired

    ISO 3166-1 alpha-2 country code, uppercase.

  • ibanstring | nullrequired

    IBAN of the relation.

  • btwNummerstring | nullrequired

    VAT identification number.

  • kvkNummerstring | nullrequired

    Dutch Chamber of Commerce number.

  • documenttaalstring | nullrequired

    Language its documents are issued in. null means: follow the administration default.

  • standaardBetaaltermijnDageninteger | nullrequired

    Default payment term in days, used when an invoice does not specify one.

  • aangemaaktOpstring (date-time)required

    Creation timestamp.

  • bijgewerktOpstring (date-time)required

    Last modification timestamp.

RelatieLijst

A page of relations.

  • dataarray of Relatierequired
  • volgendeCursorstring (uuid) | nullrequired

    Pass as cursor to fetch the next page. null means this was the last page.

WebhookAbonnement

A webhook subscription. The signing secret is never returned here.

  • idstring (uuid)required

    Subscription id.

  • urlstring (uri)required

    Endpoint that receives the POST.

  • omschrijvingstring | nullrequired

    Free-text label.

  • gebeurtenissenarray of stringrequired

    Events this endpoint is subscribed to.

    factuur.verzondenfactuur.betaald

  • actiefbooleanrequired

    Disabled subscriptions receive nothing.

  • aangemaaktOpstring (date-time)required

    Creation timestamp.

WebhookAbonnementMetGeheim

A freshly created subscription. This is the only response that ever carries geheim — store it now; we keep it encrypted and cannot show it again.

  • idstring (uuid)required

    Subscription id.

  • urlstring (uri)required

    Endpoint that receives the POST.

  • omschrijvingstring | nullrequired

    Free-text label.

  • gebeurtenissenarray of stringrequired

    Events this endpoint is subscribed to.

    factuur.verzondenfactuur.betaald

  • actiefbooleanrequired

    Disabled subscriptions receive nothing.

  • aangemaaktOpstring (date-time)required

    Creation timestamp.

  • geheimstringrequired

    Signing secret (whsec_…). Shown exactly once.

WebhookLijst

All webhook subscriptions of this administration (no paging).

  • dataarray of WebhookAbonnementrequired

WebhookPayload

The body we POST to your endpoint.

  • idstring (uuid)required

    Delivery id. Delivery is at-least-once — deduplicate on this value.

  • gebeurtenisstringrequired

    Which event this is.

    factuur.verzondenfactuur.betaald

  • tijdstipstring (date-time)required

    When the event occurred (not when this attempt was made).

  • administratieIdstring (uuid)required

    The administration the event belongs to.

  • dataobjectrequired

    A snapshot taken at the moment of the event. A retry never recomputes it, so a redelivery carries the original values.

  • factuurFactuurSamenvattingrequired

    An invoice without its lines. This is the exact shape used in webhook payloads, so what a consumer receives is what the GET returns.