Dit artikel is bedoeld voor de persoon die de ontvangst van PassPass-webhooks implementeert. Voor een overzicht en de configuratie vanuit het dashboard, raadpleeg Publieke API en Webhooks.
De gegevens begrijpen
Elke webhook bevat een of meer gegevensblokken. Hier is wat elk vertegenwoordigt.
Order (bestelling) : een transactie uitgevoerd door een koper. Bevat het totale bedrag, de betalingsstatus, de identiteit van de koper, de details van de gekochte tickets (line_items), en eventueel de bedrijfsfacturatiegegevens (billing).
Attendees (deelnemers) : een item per ticket in de bestelling. Als een koper 3 tickets neemt, zullen er 3 vermeldingen in attendees zijn. Elke vermelding bevat het type ticket, de status van het ticket, en afhankelijk van uw permissies: de contactgegevens van de deelnemer, de betaalde prijs, de antwoorden op het formulier, en de toegangstoken van het ticket.
Event (evenement) : de basisinformatie van uw PassPass-evenement (titel, data, locatie, status). Dit blok is identiek in alle typen webhooks.
Billing (facturatie) : de bedrijfsgegevens van de koper (naam, BTW, adres). Alleen aanwezig als de koper "ik bestel namens een bedrijf" heeft aangevinkt bij het afrekenen. Anders null.
Form answers (formulierantwoorden) : de antwoorden op de aangepaste velden die u hebt geconfigureerd. Aanwezig op twee niveaus: op bestelniveau (form_answers in order) en op deelnemersniveau (form_answers in elke attendee).
Access token (toegangstoken) : de geheime identificatie achter de QR-code van een ticket, gelezen door de mobiele applicatie PassPass Organizer bij de toegangscontrole. Deze token kan bijvoorbeeld opnieuw worden toegewezen op een badge om een QR-code te repliceren die leesbaar is door de PassPass-ticketcontrole. Wees voorzichtig bij het gebruik ervan: het doorgeven van deze token komt neer op het geven van toegang tot het ticket.
Beschikbare evenementen
Elk type evenement stuurt verschillende gegevensblokken:
order.created : bestelling aangemaakt (voor betaling) → order, event, attendees
order.paid : betaling bevestigd (inclusief ook voltooide gratis bestellingen) → order, event, attendees
order.cancelled : bestelling geannuleerd → order, event, attendees
order.expired : winkelwagen verlopen (25 min zonder betaling) → order, event, attendees (leeg)
ticket.validated : ticket gescand bij de ingang → scan, participant
event.published : evenement gepubliceerd → event
event.updated : evenement gewijzigd → event, changed_fields
Permissies (scopes)
De gegevens die in de payload zijn opgenomen, zijn afhankelijk van de permissies die op uw endpoint zijn geconfigureerd. Een niet-geautoriseerd veld is afwezig in de payload, niet null.
events:read : event-blok (titel, data, locatie, status)
orders:read : basis order-blok (bedrag, koper, billing, line items, status)
orders:read:financial : service_fee, payout_amount, refundable_amount, historical_service_fee
orders:read:form_answers : antwoorden op het bestelformulier
attendees:read : minimaal deelnemersblok (ID, ticket, status, data)
attendees:read:contact : first_name, last_name, email
attendees:read:financial : ticket_price
attendees:read:form_answers : antwoorden op het deelnemersformulier
attendees:read:full : access_token (inclusief automatisch contact en financieel)
access_token is de geheime identificatie achter de QR-code van het ticket. Vraag attendees:read:full alleen aan als uw systeem het echt nodig heeft.
Structuur van een payload
Elke webhook is een POST JSON met deze enveloppe:
{
"type": "order.paid",
"created_at": "2026-04-17T10:00:00.000Z",
"data": { }
}
Conventies:
Alle velden zijn in snake_case
De bedragen zijn in euro's (niet in centen).
25= 25,00€De datums zijn in ISO 8601 UTC
Meertalige velden zijn objecten
{"fr": "...", "en": "...", "nl": "..."}Uitzondering: de blokken buyer en billing zijn in camelCase
De payload kan nieuwe velden ontvangen zonder voorafgaande kennisgeving. Weiger geen onbekend veld
Volledig voorbeeld: order.paid
Geactiveerd wanneer een betaling is bevestigd, of wanneer een gratis bestelling is voltooid. Dit is het meest gebruikte evenement: het bevestigt dat een deelnemer zijn inschrijving heeft voltooid. Gebruiksscenario: voeg de deelnemer toe aan uw CRM, stuur een welkomstmail, werk uw boekhouding bij, start een workflow in Zapier/Make/n8n.
{
"type": "order.paid",
"created_at": "2026-04-17T10:00:00.000Z",
"data": {
"order": {
"id": 65817,
"event_id": 1535,
"status": "paid",
"origin": "online",
"currency": "EUR",
"amount_total": 200,
"buyer": {
"firstName": "Marie",
"lastName": "Dupont",
"email": "marie@example.com"
},
"billing": {
"identifierType": "vat",
"companyName": "Demo Corp SRL",
"vat": "BE0123456789",
"vatCountry": "BE",
"addressStreet": "Rue de la Loi 1",
"addressCity": "Bruxelles",
"addressZip": "1000",
"addressCountry": "BE"
},
"line_items": [
{
"type": "ticket",
"ticket_id": 4525,
"name": {"fr": "Entrée Standard"},
"quantity": 2,
"unit_amount": 100,
"subtotal": 200
}
],
"form_answers": null,
"paid_at": "2026-04-17T10:00:00.000Z",
"cancelled_at": null,
"created_at": "2026-04-17T10:00:00.000Z",
"updated_at": "2026-04-17T10:00:00.000Z",
"service_fee": 4.6,
"payout_amount": 195.4,
"refundable_amount": 4.6,
"historical_service_fee": 4.6
},
"event": {
"id": 1535,
"title": {"fr": "Mon événement"},
"short_description": {"fr": "Description courte"},
"address_label": {"fr": "Bruxelles"},
"locality_type": "physical",
"start_at": "2026-06-01T18:00:00.000Z",
"end_at": "2026-06-01T23:00:00.000Z",
"timezone": "Europe/Brussels",
"currency": "EUR",
"status": "published",
"visibility": "public"
},
"attendees": [
{
"id": "019fdb0d-...",
"event_id": 1535,
"order_id": 65817,
"status": "active",
"ticket": {
"id": 4525,
"type": "ticket",
"name": {"fr": "Entrée Standard"}
},
"first_name": "Marie",
"last_name": "Dupont",
"email": "marie@example.com",
"ticket_price": 100,
"access_token": "019fdb0d-...",
"created_at": "2026-04-17T10:00:00.000Z",
"updated_at": "2026-04-17T10:00:00.000Z",
"form_answers": [
{
"type": "selection",
"field_id": "abc123...",
"question_title": {"fr": "Quel est votre statut actuel ?"},
"answer": {"fr": "Indépendant"}
},
{
"type": "unique-choice",
"field_id": "def456...",
"question_title": {"fr": "Régime alimentaire"},
"answer": {"fr": "Végétarien"}
},
{
"type": "short-text",
"field_id": "ghi789...",
"question_title": {"fr": "Nom entreprise"},
"answer": "Demo Corp"
},
{
"type": "formatted-field",
"field_id": "jkl012...",
"question_title": {"fr": "Téléphone"},
"answer": "+32400000000"
},
{
"type": "formatted-field",
"field_id": "mno345...",
"question_title": {"fr": "Code postal"},
"answer": "1000"
}
]
}
]
}
}
Voorbeeld: ticket.validated
Geactiveerd wanneer een ticket wordt gescand via de mobiele applicatie PassPass Organizer. Gebruiksscenario: een aanwezigheidsbord in realtime bijwerken, een badge afdrukken, een melding naar uw team sturen bij de aankomst van een VIP.
{
"type": "ticket.validated",
"created_at": "2026-04-17T10:00:00.000Z",
"data": {
"scan": {
"id": "019d-...",
"type": "scan",
"direction": "enter",
"created_at": "2026-04-17T10:00:00.000Z"
},
"participant": {
"id": "019fdb0d-...",
"event_id": 1535,
"order_id": 65817,
"status": "active",
"ticket": {
"id": 4525,
"type": "ticket",
"name": {"fr": "Entrée Standard"}
},
"first_name": "Marie",
"last_name": "Dupont",
"email": "marie@example.com",
"ticket_price": 100,
"created_at": "2026-04-17T10:00:00.000Z",
"updated_at": "2026-04-17T10:00:00.000Z"
}
}
}
Voorbeeld: event.updated
Geactiveerd wanneer een gevolgd veld van uw evenement wordt gewijzigd (titel, data, locatie, beschrijving, status...). De payload bevat de status van het evenement na wijziging en de lijst met gewijzigde velden. Gebruiksscenario: synchroniseer de evenementinformatie op uw website, informeer uw team over een datum- of locatieverandering.
{
"type": "event.updated",
"created_at": "2026-04-17T10:00:00.000Z",
"data": {
"event": {
"id": 1535,
"title": {"fr": "Mon événement (modifié)"},
"short_description": {"fr": "Nouvelle description"},
"address_label": {"fr": "Bruxelles"},
"locality_type": "physical",
"start_at": "2026-06-01T18:00:00.000Z",
"end_at": "2026-06-01T23:00:00.000Z",
"timezone": "Europe/Brussels",
"currency": "EUR",
"status": "published",
"visibility": "public"
},
"changed_fields": ["title", "short_description"]
}
}
Handtekeningverificatie
Elke aanvraag bevat een handtekeningheader:
x-passpass-signature: t=1776412800,v1=9f2c...
De handtekening is een HMAC-SHA256 berekend met uw geheim (whsec_...) op de string <timestamp>.<ruwe inhoud van de aanvraag>.
Node.js :
const crypto = require('crypto');
function verify(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(
header.split(',').map((kv) => kv.split('='))
);
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1 || '', 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python :
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(kv.split("=", 1) for kv in header.split(","))
ts = int(parts.get("t", 0))
if not ts or abs(time.time() - ts) > tolerance:
return False
expected = hmac.new(
secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
Weiger aanvragen waarvan de timestamp ouder is dan 5 minuten.
HTTP-headers
Elke webhook-aanvraag bevat deze headers:
x-passpass-event : type evenement (bijv.
order.paid). Maakt routering mogelijk zonder de inhoud te parserenx-passpass-signature : HMAC-handtekening om de authenticiteit te verifiëren
x-passpass-timestamp : Unix-timestamp (seconden) gebruikt in de handtekening
x-passpass-idempotency-key : deduplicatiesleutel. Stabiel voor hetzelfde evenement, inclusief bij herhalingen. Dedupliceer op dit veld aan de ontvangende kant
x-passpass-delivery-id : unieke identificatie van de leveringspoging. Handig voor debugging
Herhalingen en deactivering
Uw endpoint moet antwoorden HTTP 2xx binnen 10 seconden. Elke andere code, een timeout of een netwerkfout telt als een mislukking.
Bij een mislukking probeert PassPass 6 keer opnieuw met een exponentiële backoff: 1, 2, 4, 8, 16, 32 minuten.
Na 20 opeenvolgende mislukkingen, wordt het endpoint automatisch gedeactiveerd. Het moet handmatig opnieuw worden geactiveerd vanuit uw dashboard. Een succes reset de teller naar nul.
Bevestig onmiddellijk ontvangst (HTTP 200) en verwerk op de achtergrond. Als de verwerking lang duurt, laat PassPass dan niet wachten.
Test uw endpoint
Vanuit uw dashboard (Instellingen → Ontwikkelaars), klik op Testen op uw endpoint. PassPass stuurt een demonstratiepayload naar uw URL om de verbinding en handtekeningverificatie te valideren.
De testpayloads gebruiken herkenbare identificaties: ID's vanaf 9 999 000, email demo@passpass.be, namen "Demo ...".
De leveringsgeschiedenis is toegankelijk vanuit uw dashboard. Elke verzending (succesvol of mislukt) wordt daar geregistreerd met de antwoordcode van uw server.
Voor technische vragen of behoefte aan begeleiding, neem gerust contact op met onze support.