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 aanmakenIn éé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 -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"
}'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.$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);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_xxxxxxxxxxxxxxxxxxxxEerst 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/letters | Send a letter |
| GET | /api/v1/letters | List letters, newest first |
| GET | /api/v1/letters/{id} | One letter, with its timeline |
| POST | /api/quote | Price, and what that country can be sent(no key) |
| GET | /api/countries | Destinations and their address rules(no key) |
| GET | /api/v1/openapi.json | The 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.
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 } }| 401 | unauthorised | No key, a revoked key, or a malformed header. |
| 400 | invalid_request | The body failed validation. `details` names the fields. |
| 402 | insufficient_balance | The wallet is short. `details` carries required and available. |
| 409 | no_route | That product is not sold to that country. Ask /api/quote first. |
| 413 | too_many_pages | Over the sheet limit for that destination. |
| 404 | not_found | No 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.