Naar de inhoud
Passavo
Menu

Bouw je eigen koppeling

Je catalogus, je vrije plaatsen, je bestellingen, je tickets en je scans kan een ander programma ook ophalen, en bestellingen kan het zelf aanmaken. Hieronder staat hoe dat werkt — eerst in gewone taal, daarna met de code erbij.

Basisadres
https://passavo.eu/api/v1
Huidige versie
2026-09-20
De beschrijving zelf
openapi.json

Wat is een API, en heb je er een nodig?

Een API is een deur aan de achterkant van je account. Waar jij in je beheerscherm klikt, kan een programma langs die deur rechtstreeks je gegevens ophalen of erin schrijven — zonder dat er iemand aan een scherm zit.

Je hebt er alleen een nodig wanneer je iets wil dat het beheerscherm niet doet: cijfers in je eigen systeem, plaatsen op je eigen website, tickets uit een kassa die je al hebt. Kan je het klikken, dan is klikken sneller.

Wie zo’n koppeling bouwt — je webbouwer, je boekhoudpakket, iemand die kan programmeren — heeft twee dingen van je nodig: het adres hieronder en een sleutel die je zelf aanmaakt.

Wat je ermee kan bouwen

Vrije plaatsen op je eigen site

Toon op je eigen homepagina welke rondleidingen deze week nog plaats hebben, in je eigen vormgeving, met de cijfers die op dat moment kloppen.

Je boekhouding voeden

Laat je boekhouder of je boekhoudpakket zelf de verkoop van vorige maand ophalen, in plaats van elke maand een export door te sturen.

Een scherm aan de ingang

Een tablet in de inkomhal die toont welk tijdslot er zo begint en hoeveel plaatsen er nog vrij zijn. Eén oproep per minuut volstaat.

Verkopen vanuit je eigen systeem

Maak een bestelling aan vanuit het programma waarin je al werkt, stuur de koper een betaallink en krijg via een webhook te horen dat hij betaald heeft.

De API hoort bij de formule Pro

Sleutels maak je aan onder Instellingen → API-toegang. Dat scherm staat in je menu zodra je organisatie op die formule zit; overschakelen doe je zelf in je beheerscherm, onder Abonnement.

Bekijk de formules →

Eerst veilig proberen

Naast een gewone sleutel (pv_live_) kan je een testsleutel aanmaken (pv_test_). Die werkt met je echte catalogus, tijdsloten en capaciteit, en kan ook bestellingen aanmaken. Zo’n testbestelling gaat nooit langs een betaalprovider — een testpagina speelt de betaling na —, telt niet mee in je omzet en wordt na 24 uur opgeruimd. Tot dan houdt ze echte plaatsen vast, dus werk liefst met een product dat je daarvoor aanmaakt. Wat buiten de sandbox valt, zoals webhooks beheren, weigeren we voor een testsleutel met de code sandbox.

Snelstart in vijf stappen

Van niets naar een koppeling die weet dat er betaald is. Elke stap duurt minuten, niet dagen.

  1. Maak een sleutel aan

    Open in je beheerscherm Instellingen → API-toegang en maak een sleutel met alleen de rechten die je nodig hebt. Het volledige token zie je één keer; bewaar het zoals je een wachtwoord bewaart.

  2. Doe je eerste oproep

    Vraag GET /me op met je sleutel in de Authorization-header. Je krijgt je organisatie, je formule en de rechten van je sleutel terug. Werkt dit, dan werkt de rest ook.

  3. Maak een bestelling aan

    Post de regels die je wil verkopen naar POST /orders, met een Idempotency-Key erbij — die is hier verplicht. Gaat je verbinding onderweg stuk, dan levert dezelfde oproep opnieuw geen tweede bestelling op. Het veld reserved_until zegt tot wanneer de plaatsen voor je vastgehouden worden.

  4. Stuur de koper naar de betaallink

    Vraag met POST /orders/{id}/checkout de betaallink op, samen met het adres waar de koper na het betalen terechtkomt. Stuur de koper naar die link of toon hem in je eigen pagina. De betaling loopt via de provider die je al gekoppeld hebt; zodra ze binnen is, worden de tickets aangemaakt en verstuurd.

  5. Laat een webhook je verwittigen

    Zet een adres van je eigen site klaar en registreer het. Zodra er betaald, geannuleerd of gescand is, krijg je dat daar binnen — je hoeft niet te blijven vragen of er al iets gebeurd is.

curl

curl "https://passavo.eu/api/v1/me" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/me');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/me', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/me", headers=headers)
data = response.json()["data"]

Authenticatie

Elk verzoek draagt je sleutel als bearer-token. Een sleutel hoort bij één organisatie: die organisatie is de hele wereld van dat verzoek, en je kan er nooit per ongeluk gegevens van iemand anders mee ophalen.

Authorization: Bearer pv_live_YOUR_API_KEY

Een gewone sleutel begint met pv_live_, een sandboxsleutel met pv_test_. Dat voorvoegsel is geen geheim maar een hulp: wie het in een logboek ziet, weet meteen waar het vandaan komt.

Het volledige token bestaat één moment, bij het aanmaken. Daarna bewaren wij er alleen een onomkeerbare afdruk (sha256) van. Kwijt is dus echt kwijt: trek de sleutel in en maak een nieuwe.

Rechten

Een sleutel zonder rechten mag niets. Dat is met opzet omgekeerd aan wat je zou verwachten: deze sleutels worden door klanten uitgedeeld aan tools van derden, en dan hoort de standaard "niets" te zijn en niet "alles".

read
Alles opvragen: je locaties, producten, prijzen, vrije plaatsen, kortingen, cadeaubonnen en jaarkaarten, en ook je bestellingen, tickets, kopers en de scans aan de ingang.
write
Bestellingen aanmaken, laten afrekenen, annuleren en terugbetalen; tickets intrekken of opnieuw versturen; bezoekers inchecken; cadeaubonnen en jaarkaarten uitgeven.
webhooks
Je eigen webhook-bestemmingen beheren.
external_payment
Een bestelling die geld kost als betaald markeren zonder betaalprovider, bijvoorbeeld na een overschrijving of een contante betaling (POST /orders/{id}/mark-paid). Een apart recht naast write, zodat je het alleen geeft aan een kassa of een boekhouding die je zelf beheert. Een bestelling van nul euro bevestigen kan ook zonder.

Als er iets misgaat

Elke fout heeft dezelfde vorm, met een code die vastligt en een tekst die vertaald is. Lees de code en niet de tekst: de tekst mag herschreven worden, de code nooit.

{
  "error": {
    "code": "validation_failed",
    "message": "…",
    "details": {}
  }
}
Code Status Wanneer
unauthenticated 401 Geen, onbekende, ingetrokken of vervallen sleutel.
forbidden 403 De sleutel klopt, maar mag dit niet.
plan_required 403 De formule van deze organisatie bevat de API niet.
sandbox 403 Een testsleutel probeert iets wat buiten de sandbox valt, zoals webhooks beheren.
not_found 404 Bestaat niet, of bestaat niet binnen deze organisatie.
validation_failed 422 De vraag zelf klopt niet: een onbekende filter, een ongeldige datum.
rate_limited 429 Te veel oproepen in één minuut.
conflict 409 Dezelfde Idempotency-Key werd al gebruikt voor iets anders.
server_error 500 Er ging iets mis aan onze kant.
slot_unavailable 409 Het tijdslot is vol, al begonnen of onbekend, of er werd er geen gekozen terwijl het product er een vraagt.
sold_out 409 Het product is uitverkocht of niet meer te koop.
discount_invalid 422 De kortingscode bestaat niet, is op, of geldt hier niet.
payment_provider_missing 409 De organisatie heeft geen betaalprovider gekoppeld.
not_cancellable 409 De bestelling kan in haar huidige toestand niet geannuleerd worden.
not_refundable 409 Er valt op deze bestelling niets (meer) terug te betalen, of ze werd buiten de betaalprovider om betaald.
check_in_duplicate 409 Deze code is al ingecheckt.
check_in_invalid 404 Deze code is onbekend.
check_in_wrong_day 409 Deze code is geldig, maar niet vandaag.
check_in_cancelled 409 Deze code hoort bij een geannuleerd of terugbetaald ticket.

Een rij van een andere organisatie geeft altijd not_found en nooit een melding dat je er niet bij mag. Zou dat verschil zichtbaar zijn, dan kan iedereen met een geldige sleutel uitvissen hoeveel producten de buurman heeft door nummers af te lopen.

Paginering, sorteren en filteren

Lijsten komen per pagina binnen. Je vraagt niet om pagina drie maar om wat er ná de vorige pagina komt: het antwoord draagt een cursor, en die stuur je mee om verder te lezen. Zo mist een lijst nooit rijen wanneer er tijdens het bladeren iets bijkomt.

Parameter Wat het doet
page[size] Hoeveel rijen per pagina; standaard 25, hoogstens 100.
page[cursor] De next_cursor uit het vorige antwoord. Is hij leeg, dan was dat de laatste pagina.
sort Waarop gesorteerd wordt. Een streepje ervoor keert de volgorde om.
filter[…] Filteren op een veld dat het endpoint toestaat.
include Relaties meenemen in hetzelfde antwoord, gescheiden door komma’s.

Een parameter die het endpoint niet kent, is een fout en geen stilte. Wie zich verschrijft in de naam van een filter, hoort dat te horen — en niet ongemerkt de hele lijst ongefilterd terug te krijgen.

Er staat bewust geen totaal in het antwoord: bij tienduizenden rijen kost die telling meer dan de pagina zelf.

{
  "data": [ … ],
  "meta": { "next_cursor": "…" }
}

Twee keer versturen, één keer uitvoeren

Stuur bij elke oproep die iets aanmaakt of wijzigt een Idempotency-Key mee — een willekeurige unieke tekenreeks. Komt dezelfde oproep met dezelfde sleutel nog eens binnen, dan krijg je exact hetzelfde antwoord terug zonder dat er iets tweede keer gebeurt. Bij POST /orders is ze verplicht.

Idempotency-Key: 6f1c0b4a-6b2f-4a1b-9c7e-1f2d3e4a5b6c

Dezelfde sleutel met een andere inhoud levert conflict op en er gebeurt niets. Een bewaarde sleutel gaat 24 uur mee, en geldt per organisatie.

Versies

De versie staat in het pad (/api/v1) en verandert alleen bij een breuk; een tweede versie draait er dan naast en niet doorheen. Elke kleine toevoeging wordt gedateerd in de header Passavo-Version, nu 2026-09-20.

Wat er binnen deze versie bij mag komen: een veld, een endpoint, een nieuwe sorteer- of filterwaarde, een nieuwe foutcode. Bouw je koppeling dus zo dat een onbekend veld haar niet omver duwt.

Wat er nooit gebeurt binnen deze versie: een veld weghalen of hernoemen, een type wijzigen, een code een andere betekenis geven, of een parameter verplicht maken.

Snelheidslimiet

Een sleutel mag 120 oproepen per minuut doen. De limiet geldt per sleutel en niet per adres, zodat twee klanten achter dezelfde cloudprovider elkaar niet in de weg zitten.

Elk antwoord vertelt hoe je ervoor staat in X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Zit je eroverheen, dan krijg je rate_limited met een Retry-After erbij; wacht dan gewoon dat aantal seconden.

Codevoorbeelden

Hetzelfde verzoek in vier talen, per groep endpoints. Vervang YOUR_API_KEY door je eigen sleutel — en zet die nooit in code die een bezoeker kan downloaden.

Account

Wie ben ik en wat mag ik.

GET /api/v1/me

curl

curl "https://passavo.eu/api/v1/me" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/me');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/me', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/me", headers=headers)
data = response.json()["data"]

Catalogus

Locaties, producten en prijzen.

GET /api/v1/events

curl

curl "https://passavo.eu/api/v1/events" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/events');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/events', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/events", headers=headers)
data = response.json()["data"]

Beschikbaarheid

Tijdsloten en vrije plaatsen.

GET /api/v1/products/{id}/slots

curl

curl "https://passavo.eu/api/v1/products/12/slots" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/products/12/slots');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/products/12/slots', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/products/12/slots", headers=headers)
data = response.json()["data"]

Verkoop

Kortingen, cadeaubonnen en jaarkaarten.

GET /api/v1/discounts

curl

curl "https://passavo.eu/api/v1/discounts" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/discounts');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/discounts', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/discounts", headers=headers)
data = response.json()["data"]

Bestellingen

Bestellingen aanmaken, afrekenen, annuleren en terugbetalen.

GET /api/v1/orders

curl

curl "https://passavo.eu/api/v1/orders" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/orders');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/orders', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/orders", headers=headers)
data = response.json()["data"]

Tickets

Tickets opvragen, intrekken en opnieuw versturen.

GET /api/v1/tickets

curl

curl "https://passavo.eu/api/v1/tickets" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/tickets');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/tickets', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/tickets", headers=headers)
data = response.json()["data"]

Ingang

Inchecken aan de ingang.

GET /api/v1/check-ins

curl

curl "https://passavo.eu/api/v1/check-ins" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/check-ins');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/check-ins', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/check-ins", headers=headers)
data = response.json()["data"]

Klanten

Kopers, samengevat uit de bestellingen.

GET /api/v1/customers

curl

curl "https://passavo.eu/api/v1/customers" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/customers');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/customers', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/customers", headers=headers)
data = response.json()["data"]

Meta

De beschrijving van de API zelf.

GET /api/v1/openapi.json

curl

curl "https://passavo.eu/api/v1/openapi.json" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->get('https://passavo.eu/api/v1/openapi.json');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/openapi.json', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json"}
response = requests.get("https://passavo.eu/api/v1/openapi.json", headers=headers)
data = response.json()["data"]

Webhooks

Berichten die wij naar jouw server sturen.

GET /api/v1/webhook-deliveries

curl

curl "https://passavo.eu/api/v1/webhook-deliveries" \
  -H "Authorization: Bearer pv_live_YOUR_API_KEY" \
  -H "Accept: application/json"

PHP

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()
    ->withToken('pv_live_YOUR_API_KEY')
    ->get('https://passavo.eu/api/v1/webhook-deliveries');

$data = $response->json('data');

JavaScript

const response = await fetch('https://passavo.eu/api/v1/webhook-deliveries', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer pv_live_YOUR_API_KEY',
  },
});

const { data } = await response.json();

Python

import requests

headers = {"Accept": "application/json", "Authorization": "Bearer pv_live_YOUR_API_KEY"}
response = requests.get("https://passavo.eu/api/v1/webhook-deliveries", headers=headers)
data = response.json()["data"]

Webhooks

Een webhook is het omgekeerde van een oproep: in plaats van dat jij blijft vragen of er al betaald is, sturen wij een bericht naar een adres van jou zodra het gebeurt. Dat scheelt duizenden oproepen per dag en je weet het meteen in plaats van bij de volgende ronde.

Je beheert je bestemmingen in je beheerscherm onder Instellingen → API-toegang, tabblad Webhooks, of via de API met een sleutel die het recht webhooks draagt. Elke bestemming heeft een eigen geheim; daarmee reken je na dat een bericht echt van ons komt.

De handtekening narekenen

Elk bericht draagt de header Passavo-Signature in de vorm t=<unix>,v1=<hmac-sha256>. t is het moment waarop wij tekenden; v1 is een HMAC-SHA256 met het geheim van die bestemming over de tijdstempel, een punt en de ruwe body. Reken hem zelf na en vergelijk in constante tijd — een gewone vergelijking verraadt via de tijd die ze kost hoeveel tekens er klopten. Weiger ook een bericht waarvan t meer dan 300 seconden van je eigen klok afwijkt: zo kan niemand een opgevangen bericht later opnieuw afspelen.

Reken na op de RUWE body, vóór je framework er json van maakt. Json opnieuw samenstellen zet sleutels in een andere volgorde en dan klopt geen enkele handtekening meer.

curl

# De handtekening narekenen vanaf de opdrachtregel.
# T is de waarde van t= uit de header Passavo-Signature,
# het resultaat hoort gelijk te zijn aan de waarde van v1=.
printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET"

PHP

// De RUWE body, niet $request->all().
$body = $request->getContent();

// "t=1758355200,v1=9f86d0…" uit elkaar halen.
$delen = [];
foreach (explode(',', (string) $request->header('Passavo-Signature')) as $stuk) {
    [$sleutel, $waarde] = array_pad(explode('=', trim($stuk), 2), 2, '');
    $delen[$sleutel] = $waarde;
}

$t = $delen['t'] ?? '';
$verwacht = hash_hmac('sha256', $t . '.' . $body, $secret);

// hash_equals vergelijkt in constante tijd; de tijdstempel
// houdt een opgevangen bericht tegen dat later opnieuw komt.
if (! ctype_digit($t) || abs(time() - (int) $t) > 300 || ! hash_equals($verwacht, $delen['v1'] ?? '')) {
    abort(400);
}

JavaScript

import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody: de ruwe body als string, bv. via express.raw().
const header = request.headers['passavo-signature'] ?? '';
const delen = Object.fromEntries(header.split(',').map((s) => s.trim().split('=', 2)));

const verwacht = createHmac('sha256', secret).update(delen.t + '.' + rawBody).digest('hex');
const gekregen = delen.v1 ?? '';

const vers = /^\d+$/.test(delen.t ?? '')
  && Math.abs(Date.now() / 1000 - Number(delen.t)) <= 300;
const geldig = vers
  && verwacht.length === gekregen.length
  && timingSafeEqual(Buffer.from(verwacht), Buffer.from(gekregen));

Python

import hashlib, hmac, time

# raw_body: de ruwe body als bytes, bv. request.get_data() in Flask.
header = request.headers.get("Passavo-Signature", "")
delen = dict(stuk.strip().split("=", 1) for stuk in header.split(",") if "=" in stuk)
t = delen.get("t", "")

verwacht = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()

# compare_digest vergelijkt in constante tijd.
geldig = (t.isdigit()
          and abs(time.time() - int(t)) <= 300
          and hmac.compare_digest(verwacht, delen.get("v1", "")))

De volledige lijst gebeurtenissen en hun inhoud staat in de referentie. →

Wat er verandert

Elke wijziging die het waard is om te noemen, krijgt een nieuwe datumversie. Staat je koppeling stil op een oude datum, dan blijft ze werken — de datum zegt alleen tegen welk gedrag ze gebouwd is.

  1. 2026-09-20

    De eerste versie

    Sleutels met rechten, een sandbox, één antwoordformaat en cursorpaginering. Lezen: je organisatie, locaties, producten, tijdsloten, events, kortingen, cadeaubonnen en jaarkaarten, bestellingen, tickets, kopers en scans. Schrijven: bestellingen aanmaken, afrekenen, annuleren en terugbetalen, tickets intrekken, bezoekers inchecken, cadeaubonnen en jaarkaarten uitgeven. En uitgaande webhooks, ondertekend per bestemming.