Doorgaan

Brieven versturen vanuit uw eigen systeem

Een HTTP-verzoek met tekst en een adres, en er valt een geprinte, gefrankeerde brief op de mat. Geen printer, geen postzegels, geen rit naar de brievenbus.

Sleutel aanmaken

In één aanroep

Dit is de volledige integratie. Het antwoord bevat een id waarmee u de status opvraagt, en dat id staat ook op uw factuurregel.

curl
curl -X POST https://onlinebriefversturen.nl/api/v1/letters \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Geachte heer, mevrouw,\n\nHierbij zeg ik mijn abonnement op.",
    "subject": "Opzegging",
    "sender":    { "name": "Jan de Vries", "street": "Merelstraat", "number": "64",
                   "postalCode": "8916 AX", "city": "Leeuwarden", "country": "NL" },
    "recipient": { "company": "Voorbeeld BV", "name": "Afdeling Klantenservice",
                   "street": "Hoofdstraat", "number": "1",
                   "postalCode": "1011 AA", "city": "Amsterdam", "country": "NL" },
    "product": "standard",
    "idempotencyKey": "opzegging-2026-07-28"
  }'
Node.js
const res = await fetch("https://onlinebriefversturen.nl/api/v1/letters", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SENDLETTER_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ text, sender, recipient, product: "registered",
                         idempotencyKey: invoice.id }),
})
const letter = await res.json()
// letter.id is what you store; poll it or wait for the webhook.
PHP
$ch = curl_init("https://onlinebriefversturen.nl/api/v1/letters");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("SENDLETTER_KEY"),
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "text" => $body, "sender" => $sender, "recipient" => $recipient,
    "idempotencyKey" => $invoiceId,
  ]),
]);
$letter = json_decode(curl_exec($ch), true);
Python
import os, requests

letter = requests.post(
    "https://onlinebriefversturen.nl/api/v1/letters",
    headers={"Authorization": f"Bearer {os.environ['SENDLETTER_KEY']}"},
    json={"text": body, "sender": sender, "recipient": recipient,
          "idempotencyKey": invoice_id},
    timeout=30,
).json()

Sleutels

Elk verzoek draagt een API-sleutel als bearer-token. U maakt sleutels aan in uw account en u ziet de sleutel eenmalig; wij bewaren alleen een hash. Een sleutel intrekken werkt onmiddellijk.

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx

Eerst proberen, dan pas posten

Een sleutel die begint met sk_test_ rekent en rendert de brief precies zoals een echte, maar stopt vlak voor de printer. Zo test u de hele koppeling zonder dat er iets de deur uit gaat.

Endpoints

POST/api/v1/lettersSend a letter
GET/api/v1/lettersList letters, newest first
GET/api/v1/letters/{id}One letter, with its timeline
POST/api/quotePrice, and what that country can be sent(no key)
GET/api/countriesDestinations and their address rules(no key)
GET/api/v1/openapi.jsonThe machine readable spec(no key)

Niet elk land verkoopt elk product. Vraag /api/quote en lees availableProducts: aangetekend naar Nederland bestaat bijvoorbeeld niet. Zo kunt u een optie uitzetten in plaats van hem te laten mislukken bij verzenden.

15 destinations. Registered mail: DE, BE, FR, ES, CH.

Statuses

queued
Accepted and waiting on the print partner.
submitted
Handed over to the printer.
printed
Printed and enveloped.
posted
Handed to the carrier. A response deadline runs from here.
delivered
Confirmed delivered. Registered mail only.
failed
Not sent. `statusDetail` says why, and the payment is returned.

Statusmeldingen naar uw eigen systeem

Een brief verandert een handvol keer van status, verspreid over dagen. Pollen is daarvoor de verkeerde vorm: u vraagt te vaak of u hoort het te laat. Zet een endpoint in uw account en wij melden het.

POST your-endpoint
X-SendLetter-Event: letter.posted
X-SendLetter-Signature: 9f86d081...

{ "type": "letter.posted",
  "sentAt": "2026-07-28T18:30:00.000Z",
  "letter": { "id": "AbC123", "status": "posted", "trackingCode": null, ... } }

Elke melding is ondertekend. Bereken HMAC-SHA256 over de onbewerkte body met uw secret en vergelijk dat met de header X-SendLetter-Signature. Doe dat vóór u de body parseert: parsen en opnieuw serialiseren verandert de bytes en dan klopt de handtekening nooit meer.

Node.js
import crypto from "node:crypto"

// The raw body, before any JSON parsing.
const expected = crypto.createHmac("sha256", process.env.WEBHOOK_SECRET)
  .update(rawBody, "utf8").digest("hex")
const sent = req.headers["x-sendletter-signature"]

if (expected.length !== sent.length ||
    !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sent))) {
  return res.status(401).end()
}

Fouten

Elke fout heeft dezelfde vorm: een code om op te programmeren en een bericht om te lezen. Programmeer op de code, want het bericht mag veranderen.

{ "error": { "code": "no_route",
             "message": "Registered mail has no route to NL",
             "details": null } }
401unauthorisedNo key, a revoked key, or a malformed header.
400invalid_requestThe body failed validation. `details` names the fields.
402insufficient_balanceThe wallet is short. `details` carries required and available.
409no_routeThat product is not sold to that country. Ask /api/quote first.
413too_many_pagesOver the sheet limit for that destination.
404not_foundNo such letter on this account.

OpenAPI

De volledige specificatie staat live, dus hij beschrijft nooit een versie die niet draait. Richt uw generator erop en u heeft een client in uw eigen taal.