Checkify
Developer docs

Verifieer op je backend

Nadat de browser-embed is voltooid, moet je server het resultaat verifiëren met een site-API-sleutel voordat je signup, checkout of een andere beschermde actie toestaat.

Serververificatie in 5 minuten

  1. Voeg de Checkify browser-embed toe aan je pagina.
  2. De embed start de Checkify-sessie en schrijft de verificatiereferentie in je formulier.
  3. Je backend leest de waarde van het verborgen veld.
  4. Je backend stuurt die waarde naar POST /v1/qr/results/verify als request_id.
  5. Als de vereiste claims zijn goedgekeurd, sta de beschermde actie toe.
  6. Vertrouw nooit alleen op frontend-status.

Het verborgen formulierveld kan checkify_token heten, maar de waarde is de Checkify request_id.

End-to-end-flow

1. Maak een site-API-sleutel aan

Open in je bedrijfsdashboard Developer en maak een site-API-sleutel aan voor de site die je integreert. Bewaar die alleen in serveromgevingsvariabelen — stuur hem nooit naar de browser.

Gebruik je de frontend SDK?

Je hoeft GET /v1/qr/pass/{PASS_ID}/start niet handmatig aan te roepen. De Checkify JavaScript-embed start de sessie, ontvangt de request_id en schrijft die automatisch in je formulier.

Je backend hoeft alleen:

2. Ontvang de request_id op je server

De frontend SDK schrijft de Checkify request_id in een verborgen formulierveld nadat de gebruiker verificatie heeft voltooid. De standaardveldnaam is checkify_token — dat is de veldnaam, geen apart tokentype. Stuur de veldwaarde naar het verify-endpoint als request_id.

Mobile app-handoff kan checkify_request_id teruggeven in de pagina-URL. De SDK leest die bij het laden; je server verifieert nog steeds dezelfde request_id-waarde.

Form POST vanaf je frontend

{
  "email": "user@example.com",
  "checkify_token": "56a57761-ff5b-42f0-9c97-6c13e223e017"
}

Verify-verzoek vanaf je backend

{
  "request_id": "56a57761-ff5b-42f0-9c97-6c13e223e017",
  "required_claims": ["human_verified"],
  "required_fields": [],
  "consume": true
}

Alleen handmatige frontend-integratie

3. Handmatig testen met cURL

Backend-ontwikkelaars roepen /start normaal niet aan. Gebruik dit gedeelte alleen bij een eigen frontend of bij testen zonder embed. Plaats URL’s tussen aanhalingstekens in zsh/bash zodat ? niet als glob wordt behandeld.

Origin-header is vereist voor /start

Pass start werkt alleen vanaf een geregistreerd websitedomein. Browsers sturen Origin automatisch; cURL doet dat niet. Stuur Origin (of X-Checkify-Site-Url) met een hostnaam die onder Sites → toegestane domeinen staat. De hostnaam moet exact overeenkomen — checkify.me en www.checkify.me zijn verschillend.

Meest voorkomende testprobleem

Als /start HTTP 403 teruggeeft, kan je Pass ID nog steeds geldig zijn. Meestal staat het verzoekdomein niet in toegestane domeinen, of is www.example.com geregistreerd terwijl example.com werd gebruikt (of andersom).

Gebruik een dedicated testsite en Pass in je Checkify-dashboard voor integratietests. Site-API-sleutels gebruiken het voorvoegsel csk_; er is geen apart test-sleutelformaat. Test niet tegen productie-checkout tot je zowel succes- als weigeringsflows hebt bevestigd.

# Step A — start a session (replace PASS_ID and YOUR_REGISTERED_DOMAIN)
curl -sS \
  -H "Accept: application/json" \
  -H "Origin: https://YOUR_REGISTERED_DOMAIN" \
  "https://checkify.me/v1/qr/pass/chk_live_YOUR_PASS_ID/start?request_type=human"

# Response includes request_id and qr_url — open qr_url and complete verification

# Step B — verify on your server (after the user completes verification)
curl -sS -X POST "https://checkify.me/v1/qr/results/verify" \
  -H "Authorization: Bearer $CHECKIFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "request_id": "PASTE_request_id_FROM_STEP_A",
    "required_claims": ["human_verified"],
    "consume": true
  }'

Gebruik je site-API-sleutel alleen bij de verify-aanroep. Voltooi verificatie in de app vóór je verify aanroept — anders krijg je status pending.

Vereiste claims per usecase

Stel required_claims in zodat die overeenkomen met de proof die je beschermde actie nodig heeft. Claimnamen moeten overeenkomen met wat Checkify voor die verificatiesessie heeft goedgekeurd. Leeftijdschecks gebruiken age_over_{N} (bijvoorbeeld age_over_18). Dynamische drempels van 10 tot en met 110 worden ondersteund wanneer de embed het bijbehorende verzoektype vraagt.

Usecase Voorgestelde required_claims Notities
Bot-/CAPTCHA-vervanging ["human_verified"] Bevestigt dat een echte gebruiker de Checkify-flow heeft voltooid.
13+ content of jeugdgerichte producten ["age_over_13"] Gebruik wanneer je Pass age_over_13 vraagt.
16+ content of regionale leeftijdsregels ["age_over_16"] Gebruik wanneer je Pass age_over_16 vraagt.
Vape, alcohol of 18+ checkout ["age_over_18"] Laat de leeftijdsdrempel aansluiten op je product en markt.
21+ beperkte producten (waar van toepassing) ["age_over_21"] Gebruik wanneer je Pass age_over_21 vraagt.
Challenge 25 of strenger retailbeleid ["age_over_25"] Gebruik wanneer je Pass age_over_25 vraagt.
Aangepaste leeftijdsdrempel ["age_over_N"] Gebruik age_over_N waarbij N 10–110 is, afgestemd op je embed-verzoektype.

Embed-verzoektype → serverclaim

Het browser-verzoektype moet overeenkomen met de claim die je server-side verifieert.

Embed-verzoektyperequired_claims
human["human_verified"]
age_over_13["age_over_13"]
age_over_16["age_over_16"]
age_over_18["age_over_18"]
age_over_21["age_over_21"]
age_over_25["age_over_25"]
age_over_N["age_over_N"] (N = 10–110)

Optionele required_fields

Naast required_claims kun je specifieke goedgekeurde identiteitsvelden vereisen (bijvoorbeeld land of leeftijdsband) wanneer je integratie die heeft verzameld. Geef veldnamen door in required_fields — ontbreekt er een in het goedgekeurde resultaat, dan retourneert verify verification_failed met missing_fields in error.details.

4. Roep verify aan op je server

Roep POST /v1/qr/results/verify aan met je site-API-sleutel voordat je toegang verleent. Behandel de browserreferentie (request_id of legacy poll-token) als onbetrouwbaar tot Checkify het resultaat bevestigt.

POST https://checkify.me/v1/qr/results/verify
Authorization: Bearer YOUR_SITE_API_KEY
Content-Type: application/json

{
  "request_id": "56a57761-ff5b-42f0-9c97-6c13e223e017",
  "required_claims": ["human_verified"],
  "consume": true
}

Je kunt ook token sturen in plaats van request_id. De API-sleutel is gekoppeld aan je Checkify-site — je stuurt geen site_id in de request body.

Implementatievoorbeelden

# checkify_token from your form POST is sent as request_id
curl -sS -X POST "https://checkify.me/v1/qr/results/verify" \
  -H "Authorization: Bearer $CHECKIFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "request_id": "56a57761-ff5b-42f0-9c97-6c13e223e017",
    "required_claims": ["human_verified"],
    "consume": true
  }'

Wanneer consume gebruiken

Gebruik consume: true voor definitieve beschermde acties — checkout, signup, wachtwoordreset, leeftijdsgebonden aankoop, toegang tot beschermde content

Gebruik consume: false alleen voor — testen, debuggen of niet-definitieve checks waarbij het resultaat later opnieuw moet worden geverifieerd

Voor gereguleerde of risicovolle acties geef je de voorkeur aan consume: true zodat hetzelfde verificatieresultaat niet voor meerdere beschermde beslissingen kan worden hergebruikt.

Fail closed bij gereguleerde acties

Als je backend Checkify niet kan bereiken, sta geen leeftijdsbeperkte checkout, goktoegang, adult content, vape- of alcoholaankoop of andere beschermde acties toe zonder een bevestigd server-side resultaat.

Gebruik korte HTTP-timeouts (bijvoorbeeld 10 seconden). Probeer een of twee keer opnieuw bij tijdelijke 5xx- of netwerkfouten en weiger daarna toegang. Log incidenten server-side en toon gebruikersveilige berichten. Stel geen interne Checkify-foutdetails bloot aan klanten.

try {
  const verdict = await verifyCheckifyResult(requestId);

  if (!verdict.allow) {
    return res.status(403).json({ error: "Verification required" });
  }

  // Continue protected action
} catch (err) {
  console.error("Checkify verification unavailable", err);

  return res.status(403).json({
    error: "Verification is temporarily unavailable. Please try again.",
  });
}

Server-SDK’s en tooling

Gebruik @checkify/server (npm) of checkify-server (Python) voor getypeerde verify-helpers, of roep POST /v1/qr/results/verify rechtstreeks aan. Er is nog geen gepubliceerde OpenAPI-spec — gebruik de voorbeelden op deze pagina.

@checkify/server v1.0.0 is gepubliceerd op npm. Het Python-pakket checkify-server zit in de Checkify SDK-monorepo.

Vervaltijd van resultaat

Voltooide verificaties verlopen na QR_RESULT_MAX_AGE_SECONDS (standaard 900 seconden / 15 minuten). Na verstrijken retourneert verify result_expired — vraag de gebruiker opnieuw te verifiëren.

Statuspolling-endpoints (eigen frontends)

De JavaScript SDK beheert sessiestatus voor standaardembeds. Gebruik deze alleen wanneer je een eigen frontend bouwt die GET /v1/qr/pass/{pass_id}/start handmatig aanroept.

Response-afhandeling

Bij succes retourneert Checkify HTTP 200 met success: true en status: completed. Sta toegang alleen toe wanneer vereiste claims aanwezig zijn (bijvoorbeeld human_verified: true).

{
  "success": true,
  "status": "completed",
  "message": "Verification result confirmed",
  "request_id": "56a57761-ff5b-42f0-9c97-6c13e223e017",
  "site_id": "YOUR_SITE_ID",
  "business_id": "YOUR_BUSINESS_ID",
  "approved_claims": {
    "human_verified": true
  },
  "approved_fields": [],
  "signed_result": {
    "payload": { "...": "..." },
    "signature": "...",
    "signature_algorithm": "EdDSA",
    "key_id": "checkify:default"
  }
}

Ondertekend resultaat

Het signed_result-object laat je backend een manipulatiebestendig auditrecord bewaren dat Checkify de vereiste claim op het moment van verificatie heeft goedgekeurd. De meeste integraties hebben alleen approved_claims nodig. Gereguleerde of risicovolle bedrijven kunnen signed_result ook opslaan voor audit. Sla niet meer persoonsgegevens op dan nodig.

Als de klant nog niet klaar is in de app, retourneert Checkify HTTP 200 met success: false en status: pending. Weiger beschermde acties en vraag de gebruiker verificatie te voltooien.

{
  "success": false,
  "status": "pending",
  "message": "Verification is not completed yet",
  "request_id": "56a57761-ff5b-42f0-9c97-6c13e223e017",
  "approved_claims": {},
  "signed_result": null
}
VeldBetekenis
successtrue wanneer verificatie is voltooid en requirements overeenkomen
statuscompleted of pending
approved_claimsClaims die Checkify heeft goedgekeurd, bijv. human_verified: true
signed_resultOptionele ondertekende payload voor audittrails

JSON-foutpayloads

Wanneer verificatie niet kan doorgaan, retourneert Checkify HTTP 4xx/5xx met een gestructureerde JSON-body. Controleer error.code en log error.details server-side. Geef generieke berichten terug aan eindgebruikers.

{
  "success": false,
  "error": {
    "code": "verification_failed",
    "message": "The verification did not include all required claims.",
    "details": {
      "missing_claims": ["age_over_18"]
    }
  }
}
Code HTTP Betekenis Aanbevolen actie
missing_authorization401Er is geen Authorization-header gestuurd.Stuur Authorization: Bearer YOUR_SITE_API_KEY alleen vanuit server-side code.
invalid_token401Bearer-token ontbreekt, is ongeldig of is geen geldige site-API-sleutel.Controleer de sleutel in je bedrijfsdashboard en bewaar die in omgevingsvariabelen.
expired_token401De site-API-sleutel is ingetrokken.Maak een nieuwe site-API-sleutel aan en roteer die op je servers.
missing_required_field400 / 422request_id of token ontbreekt in de JSON-body.Geef de request_id uit je verborgen formulierveld door, of het poll-token als je integratie dat nog gebruikt.
invalid_request_id400De referentie kon niet worden geparseerd of is leeg na normalisatie.Zorg dat je frontend de Checkify request_id ongewijzigd meestuurt.
result_not_found404Er bestaat geen verificatieverzoek voor die referentie.Weiger de actie. De gebruiker kan het verborgen veld hebben gemanipuleerd of een oude sessie hebben ingestuurd.
result_expired410De verificatie is te lang geleden voltooid om voor deze actie te vertrouwen.Vraag de gebruiker opnieuw te scannen en roep verify aan met de nieuwe request_id.
verification_failed403 / 409Verificatie is afgerond maar voldeed niet aan je vereiste claims/velden, hoort bij een andere site, of is al geconsumeerd.Weiger toegang. Inspecteer error.details op missing_claims, missing_fields of reason.
missing_required_claims403developers_server.err_missing_required_claims_meaningdevelopers_server.err_missing_required_claims_action
missing_required_fields403developers_server.err_missing_required_fields_meaningdevelopers_server.err_missing_required_fields_action
unknown_required_attributes400developers_server.err_unknown_required_attributes_meaningdevelopers_server.err_unknown_required_attributes_action
business_not_operational403Het bedrijfsaccount is vergrendeld, gearchiveerd of niet operationeel.Neem contact op met de bedrijfseigenaar of Checkify-support. Sta geen beschermde acties toe tot het account actief is.
validation_errors422De JSON-body is niet geslaagd voor schema-validatie (HTTP 422).Inspecteer error.details.validation_errors voor veldberichten. Corrigeer request_id-, required_claims- of required_fields-types vóór opnieuw proberen.
rate_limited429Te veel verify-aanroepen in een kort tijdsvenster.Probeer opnieuw met exponentiële backoff. Verifieer alleen bij beschermde acties, niet bij elke paginaweergave.
server_error500+Checkify kon verificatie niet voltooien door een tijdelijk serverprobleem.Probeer een of twee keer opnieuw, fail closed daarna en log het incident.

Webhooks en asynchrone verificatie

Momenteel is serververificatie synchroon: je backend verifieert de request_id wanneer de gebruiker de beschermde actie indient. Voor checkout, signup en toegangscontrole is dit de aanbevolen aanpak. Algemene verificatiewebhooks zijn niet vereist voor standaardintegraties. GoHighLevel en andere partnerintegraties kunnen aparte outbound-webhookconfiguratie in het bedrijfsdashboard gebruiken.

Veelgemaakte fouten

Beveiligingsherinnering

Behandel het verborgen veld als een onbetrouwbare referentie, niet als bewijs. Verifieer altijd met je site-API-sleutel op de server voordat je toegang verleent. Gebruik consume: true voor eenmalige acties zoals signup of wachtwoordreset.

Volgende stappen