Zo zet je een betaal-QR op je facturen

Elke factuur krijgt zijn eigen unieke QR-code met het juiste bedrag en betalingskenmerk. Kies de route die bij je past:

Spoor 1

Handig, maar geen programmeur

Drie manieren, van makkelijk naar iets avontuurlijker. Voor allemaal geldt: maak eerst een gratis account aan en vul bij Instellingen je standaard-IBAN in.

Optie A — Klikken in het dashboard (geen enkele code)

1

Maak de QR-code aan

Ga naar Dashboard → QR-codes, vul het factuurbedrag en het betalingskenmerk in (bijv. je factuurnummer) en klik op QR-code genereren.

2

Download de afbeelding

Klik in de lijst op PNG (voor Word/e-mail) of SVG (voor drukwerk) en sla de afbeelding op.

3

Plak hem op je factuur

Voeg de afbeelding in je factuursjabloon in — onderaan bij de betaalinstructies werkt het best, minimaal 2×2 cm groot. Zet er een regel bij als: “Scan de QR-code met je telefooncamera en betaal direct.”

Optie B — Afbeeldings-URL in een digitale factuur of e-mail

Verstuur je facturen als HTML-e-mail of online pagina? Dan hoef je niets te downloaden: elke QR-code heeft een vaste afbeeldings-URL die je direct in je sjabloon zet.

<img src="https://zwoep.nl/qr/JOUW-QR-ID/image?format=png&size=300"
     alt="Betaal deze factuur via QR" width="150" height="150">

Het QR-ID vind je in het dashboard bij de aangemaakte code. Let op: elke factuur = één eigen QR-code, dus dit werkt vooral handig in combinatie met optie C of D.

Optie C — Google Sheets: QR-codes uit je factuurlijst

Houd je je facturen bij in een spreadsheet? Met dit kant-en-klare script maakt Google Sheets voor elke rij automatisch een QR-code aan. Zet in kolom A het factuurnummer en in kolom B het bedrag (bijv. 149.95).

Wil je liever één keer een CSV uploaden dan een script rij voor rij te laten draaien? Zie “Batch-upload” verderop op deze pagina.

1

Maak een API-key aan

In het dashboard onder API-keys. Kopieer de key — je ziet hem maar één keer.

2

Open de scripteditor

In Google Sheets: Extensies → Apps Script. Plak het script hieronder en vul je API-key in.

3

Voer het script uit

Klik op Uitvoeren. In kolom C verschijnt de QR-code als afbeelding, in kolom D de link. Sleep de afbeelding zo je factuur in, of gebruik de kolom in een mailmerge.

const API_KEY = "zw_live_JOUW_KEY_HIER";

function maakQrCodes() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const rijen = sheet.getDataRange().getValues();

  for (let i = 1; i < rijen.length; i++) {
    const [kenmerk, bedrag, bestaandeQr] = rijen[i];
    if (!kenmerk || !bedrag || bestaandeQr) continue;

    const antwoord = UrlFetchApp.fetch("https://zwoep.nl/api/v1/qr-codes", {
      method: "post",
      contentType: "application/json",
      headers: { Authorization: "Bearer " + API_KEY },
      payload: JSON.stringify({
        amount: { value: String(bedrag), currency: "EUR" },
        reference: String(kenmerk),
      }),
    });

    const qr = JSON.parse(antwoord.getContentText());
    sheet.getRange(i + 1, 3).setFormula('=IMAGE("' + qr.links.imagePng + '")');
    sheet.getRange(i + 1, 4).setValue(qr.links.imagePng + "&size=1024");
  }
}

Gebruik je een boekhoudpakket (Moneybird, e-Boekhouden, Exact)?

Voor Moneybird staat een kant-en-klare koppeling in de ontwikkelaarssectie hieronder — die is ook door een handige niet-programmeur te draaien. Voor andere pakketten: vraag je leverancier naar een “webhook” of “koppeling per factuur”, of stuur ons een bericht, dan kijken we mee.

Heeft je pakket géén API (zoals MoneyMonk)? Dan kan het via PDF-nabewerking: een klein script leest het factuurnummer en bedrag uit de gedownloade factuur-PDF, maakt de QR aan en stempelt hem op de factuur. Zie het MoneyMonk-voorbeeld hieronder (ook direct als kant-en-klaar script te downloaden).

Spoor 2

Ontwikkelaars

REST API v1: JSON in en uit, authenticatie met een Bearer-token, cursor-paginering. Eén call per factuur bij het genereren — meer is het niet.

Kant-en-klare voorbeelden per boekhoudpakket

Moneybird heeft een publieke REST API, dus de hele keten kan volautomatisch: open facturen ophalen, per factuur een QR aanmaken, de factuur-PDF downloaden en de QR erop stempelen.

MoneyMonk heeft geen publieke API, dus een directe koppeling is niet mogelijk. Dit voorbeeld automatiseert het proces er direct achteraan: je downloadt de factuur-PDF uit MoneyMonk, en het script doet de rest.

e-Boekhouden.nl heeft een publieke REST API met sessie-gebaseerde authenticatie: elke factuur bevat een kant-en-klare PDF-downloadlink, dus ook hier kan de hele keten automatisch.

Jortt heeft een publieke REST API met OAuth2 Client Credentials-authenticatie — geen eindgebruiker-login nodig voor je eigen koppeling, en de hele keten kan automatisch.

Exact Online heeft het grootste marktaandeel in het Nederlandse mkb en een publieke REST API — maar authenticeert alleen via OAuth2 Authorization Code, dus dit voorbeeld vergt één eenmalige handmatige inlogstap.

Jouw boekhoudpakket er niet bij?

Stuur ons een bericht via support@zwoep.app — heeft het pakket een API, dan bouwen we er graag een voorbeeld bij.

Authenticatie

Maak een API-key aan in het dashboard (API-keys) en stuur hem mee in elke request:

Authorization: Bearer zw_live_...

QR-code aanmaken

POST /api/v1/qr-codes bij het genereren van de factuur. iban en beneficiary vallen terug op de standaardwaarden van het account.

# curl
curl -X POST https://zwoep.nl/api/v1/qr-codes \
  -H "Authorization: Bearer $ZWOEP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": { "value": "149.95", "currency": "EUR" },
    "reference": "Factuur 2026-0042",
    "description": "Websiteonderhoud juni",
    "expiresAt": "2026-10-01T00:00:00Z"
  }'
// Node.js / TypeScript
const response = await fetch("https://zwoep.nl/api/v1/qr-codes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ZWOEP_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: { value: "149.95", currency: "EUR" },
    reference: "Factuur 2026-0042",
  }),
});
const qr = await response.json();
// qr.links.imagePng → embed in je factuur-PDF
// qr.id            → bewaar bij de factuur voor statuschecks
# Python
import requests

qr = requests.post(
    "https://zwoep.nl/api/v1/qr-codes",
    headers={"Authorization": f"Bearer {ZWOEP_KEY}"},
    json={
        "amount": {"value": "149.95", "currency": "EUR"},
        "reference": "Factuur 2026-0042",
    },
).json()
print(qr["links"]["imagePng"])

Batch-upload (meerdere QR-codes in één call)

Moet je in één keer veel QR-codes aanmaken? POST /api/v1/qr-codes/batch accepteert een JSON-array of een CSV-bestand (max. 100 rijen per call) en meldt per rij terug of het gelukt is.

POST /api/v1/qr-codes/batch
Content-Type: application/json
Authorization: Bearer zw_live_...

[
  { "amount": { "value": "12.50", "currency": "EUR" }, "reference": "Factuur 2026-001" },
  { "amount": { "value": "8.00", "currency": "EUR" }, "reference": "Factuur 2026-002" }
]

Of als CSV — handig als je facturen al in een spreadsheet staan:

POST /api/v1/qr-codes/batch
Content-Type: text/csv
Authorization: Bearer zw_live_...

amount,reference,description
12.50,Factuur 2026-001,
8.00,Factuur 2026-002,Tweede termijn

De response meldt per rij of het gelukt is, met de index uit je array of CSV:

{
  "object": "qr_code_batch",
  "created": 2,
  "failed": 0,
  "results": [
    { "index": 0, "status": "created", "qrCode": { "...": "..." } },
    { "index": 1, "status": "created", "qrCode": { "...": "..." } }
  ]
}

CSV-kolomnamen: amount, reference (verplicht), description, iban, beneficiary, expiresAt (optioneel). Kolomvolgorde is vrij.

Gebruik in CSV een punt als decimaalteken, bijv. 12.50 — een komma wordt als kolomscheiding gelezen.

De afbeelding op de factuur

  • links.imagePng accepteert &size=128…2048 — gebruik ≥1024 voor print (2×2 cm op 300 dpi).
  • links.imageSvg is vector en dus altijd scherp — de beste keuze voor PDF-generatoren die SVG aankunnen.
  • Download de afbeelding tijdens het genereren en embed hem in de PDF; hotlinken kan, maar een factuur hoort zelfstandig leesbaar te zijn.
  • Idempotentie: maak per factuurnummer één QR aan en bewaar het id — niet bij elke PDF-regeneratie een nieuwe.

Status volgen

Pol de status om te zien of je klant de factuur heeft opgepakt — bijvoorbeeld in je aanmaanproces:

curl https://zwoep.nl/api/v1/qr-codes/{id} \
  -H "Authorization: Bearer $ZWOEP_KEY"

# of alles wat nog open staat:
curl "https://zwoep.nl/api/v1/qr-codes?status=ACTIVE&limit=100" \
  -H "Authorization: Bearer $ZWOEP_KEY"
StatusBetekenis
ACTIVEAangemaakt, nog niet gescand
SCANNEDGescand door de ontvanger (het factureerbare moment)
PAIDBetaald via de iDEAL-knop (vereist gekoppeld Mollie-account)
EXPIREDVerlopen (expiresAt verstreken)
REVOKEDIngetrokken via API of dashboard

Intrekken & fouten

curl -X DELETE https://zwoep.nl/api/v1/qr-codes/{id} \
  -H "Authorization: Bearer $ZWOEP_KEY"

Foutresponses zijn JSON met een error-object: 401 ongeldige key, 404 onbekend id, 422 validatiefout (met details per veld). Lijsten pagineren met nextCursor.

Webhooks

In plaats van te pollen kun je in het dashboard onder Webhooks één of meer endpoints registreren die automatisch een POST krijgen bij een scan of statuswijziging. Twee event-typen:

  • qr_code.scanned — alleen bij de eerste (factureerbare) scan.
  • qr_code.status_changed — bij elke statusovergang (SCANNED, PAID, EXPIRED, REVOKED); bevat altijd previousStatus/newStatus.
{
  "id": "clxyz-delivery-id",
  "type": "qr_code.status_changed",
  "createdAt": "2026-07-20T10:15:00.000Z",
  "data": {
    "qrCodeId": "clxyz...",
    "reference": "Factuur 2026-0042",
    "amount": { "value": "149.95", "currency": "EUR" },
    "previousStatus": "ACTIVE",
    "newStatus": "SCANNED"
  }
}

Elke aanroep heeft een Zwoep-Signature-header in het formaat t=<unix>,v1=<hex>, waarbij hex een HMAC-SHA256 is (met je webhook-secret als sleutel) over de string ${t}.${rawBody}:

const crypto = require("crypto");

function isValidSignature(secret, rawBody, header) {
  const [tPart, v1Part] = header.split(",");
  const timestamp = tPart.split("=")[1];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  return v1Part === `v1=${expected}`;
}

Mislukte afleveringen krijgen automatisch retries met backoff; een endpoint dat lang genoeg onbereikbaar blijft, wordt automatisch gepauzeerd en moet je handmatig heractiveren in het dashboard. Een tijdelijke storing aan jouw kant is dus geen gemiste event.

Klaar om te beginnen?

Account aanmaken is gratis, QR-codes genereren ook. Je betaalt alleen als je klant scant.

Gratis account aanmaken