Checkify
Documenti per sviluppatori

Verifica sul tuo backend

Una volta completato l'incorporamento nel browser, il tuo server deve verificare il risultato con una chiave API del sito prima di consentire la registrazione, il pagamento o qualsiasi azione protetta.

Server verification in 5 minutes

  1. Add the Checkify browser embed to your page.
  2. The embed starts the Checkify session and writes the verification reference into your form.
  3. Your backend reads the hidden field value.
  4. Your backend sends that value to POST /v1/qr/results/verify as request_id.
  5. If the required claims are approved, allow the protected action.
  6. Never trust frontend state alone.

The hidden form field may be named checkify_token, but its value is the Checkify request_id.

End-to-end flow

1. Crea una chiave API del sito

Nella dashboard aziendale, apri Sviluppatore e crea una chiave API del sito per il sito che stai integrando. Memorizzalo solo nelle variabili di ambiente del server: non inviarlo mai al browser.

Using the frontend SDK?

You do not need to manually call GET /v1/qr/pass/{PASS_ID}/start. The Checkify JavaScript embed starts the session, receives the request_id, and writes it into your form automatically.

Your backend only needs to:

2. Receive the request_id on your server

The frontend SDK writes the Checkify request_id into a hidden form field after the user completes verification. The default field name is checkify_token — that is the field name, not a separate token type. Send the field value to the verify endpoint as request_id.

Mobile app handoff may return checkify_request_id in the page URL. The SDK reads it on load; your server still verifies the same request_id value.

Form POST from your frontend

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

Verify request from your backend

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

Manual frontend integration only

3. Manual testing with cURL

Backend developers normally do not call /start. Use this section only when building a custom frontend or testing without the embed. Quote URLs in zsh/bash so ? is not treated as a glob.

Origin header is required for /start

Pass start only works from a registered website domain. Browsers send Origin automatically; cURL does not. Send Origin (or X-Checkify-Site-Url) with a hostname listed under Sites → allowed domains. The hostname must match exactly — checkify.me and www.checkify.me are different.

Most common testing issue

If /start returns HTTP 403, your Pass ID may still be valid. The usual cause is that the request domain is not listed in allowed domains, or www.example.com was registered but example.com was used (or vice versa).

Use a dedicated test site and Pass in your Checkify dashboard for integration testing. Site API keys use the csk_ prefix; there is no separate test-key format. Do not test against production checkout until you have confirmed both success and denial flows.

# 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
  }'

Use your site API key only on the verify call. Complete verification in the app before calling verify — otherwise you will get status pending.

Required claims by use case

Set required_claims to match the proof your protected action needs. Claim names must match what Checkify approved for that verification session. Age checks use age_over_{N} (for example age_over_18). Dynamic thresholds from 10 through 110 are supported when the embed requests the matching request type.

Use case Suggested required_claims Notes
Bot / CAPTCHA replacement ["human_verified"] Confirms a real user completed the Checkify flow.
13+ content or youth-oriented products ["age_over_13"] Use when your Pass requests age_over_13.
16+ content or regional age rules ["age_over_16"] Use when your Pass requests age_over_16.
Vape, alcohol, or 18+ checkout ["age_over_18"] Match the age threshold to your product and market.
21+ restricted products (where applicable) ["age_over_21"] Use when your Pass requests age_over_21.
Challenge 25 or stricter retail policy ["age_over_25"] Use when your Pass requests age_over_25.
Custom age threshold ["age_over_N"] Use age_over_N where N is 10–110, aligned with your embed request type.

Embed request type → server claim

The browser request type must match the claim you verify server-side.

Embed request typerequired_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)

Optional required_fields

Oltre a required_claims, puoi richiedere campi di identità approvati specifici (ad es. paese o fascia di età) se la tua integrazione li ha raccolti. Passa i nomi in required_fields — se mancano nel risultato approvato, verify restituisce missing_required_fields con missing_fields in error.details.

4. Call verify on your server

Call POST /v1/qr/results/verify with your site API key before granting access. Treat the browser reference (request_id or legacy poll token) as untrusted until Checkify confirms the result.

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
}

You can also send token instead of request_id. The API key is scoped to your Checkify site — you do not send site_id in the request body.

Implementation examples

# 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
  }'

When to use consume

Use consume: true for final protected actions — checkout, signup, password reset, age-gated purchase, protected content access

Use consume: false only for — testing, debugging, or non-final checks where the result must be verified again later

For regulated or high-risk actions, prefer consume: true so the same verification result cannot be reused for multiple protected decisions.

Fail closed for regulated actions

If your backend cannot reach Checkify, do not allow age-restricted checkout, gambling access, adult content access, vape or alcohol purchase, or other protected actions without a confirmed server-side result.

Use short HTTP timeouts (for example 10 seconds). Retry once or twice for transient 5xx or network errors, then deny access. Log incidents server-side and show user-safe messages. Do not expose internal Checkify error details to customers.

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 SDKs and tooling

The Checkify monorepo includes @checkify/server (Node.js) and checkify-server (Python) packages for server-side verify calls. There is no published OpenAPI spec or Postman collection yet — use the examples on this page and POST /v1/qr/results/verify directly.

@checkify/server v1.0.0 is published on npm. The Python checkify-server package ships in the Checkify SDK monorepo.

Result expiry window

Completed verifications expire after QR_RESULT_MAX_AGE_SECONDS (default 900 seconds / 15 minutes). After expiry, verify returns result_expired — ask the user to verify again.

Status polling endpoints (custom frontends)

The JavaScript SDK handles session state for standard embeds. Use these only when you build a custom frontend that calls GET /v1/qr/pass/{pass_id}/start manually.

Gestione della risposta

On success, Checkify returns HTTP 200 with success: true and status: completed. Allow access only when required claims are present (for example 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"
  }
}

Signed result

L’oggetto signed_result consente al backend di conservare un record di audit a prova di manomissione che Checkify ha approvato il claim richiesto al momento della verifica. La maggior parte delle integrazioni necessita solo di approved_claims. Aziende regolamentate o ad alto rischio possono anche memorizzare signed_result. proof_metadata annidato può includere evidenza crittografica od operativa (metadati di assurance, tipi di evidence o hash di consenso) — non documenti di identità né data di nascita grezza. Non trattare i campi di assurance annidati come certificazione o garanzia di fiducia; controlla certification_status e la tua policy. Non memorizzare più dati personali del necessario.

If the customer has not finished in the app yet, Checkify returns HTTP 200 with success: false and status: pending. Deny protected actions and ask the user to complete verification.

{
  "success": false,
  "status": "pending",
  "message": "Verification is not completed yet",
  "request_id": "56a57761-ff5b-42f0-9c97-6c13e223e017",
  "approved_claims": {},
  "signed_result": null
}
CampoSignificato
successtrue quando la verifica è completata e requisiti Rivendicazioni corrispondenti
statuscompleted o pending
approved_claimsCheckify approvate, ad es. human_verified: true
signed_resultPayload firmato opzionale per audit trail

JSON error payloads

When verification cannot proceed, Checkify returns HTTP 4xx/5xx with a structured JSON body. Check error.code and log error.details server-side. Return generic messages to end users.

{
  "success": false,
  "error": {
    "code": "verification_failed",
    "message": "The verification did not include all required claims.",
    "details": {
      "missing_claims": ["age_over_18"]
    }
  }
}
Code HTTP Meaning Recommended action
missing_authorization401No Authorization header was sent.Send Authorization: Bearer YOUR_SITE_API_KEY from server-side code only.
invalid_token401Bearer token missing, malformed, or not a valid site API key.Verify the key in your business dashboard and store it in environment variables.
expired_token401The site API key has been revoked.Create a new site API key and rotate it on your servers.
missing_required_field400 / 422request_id or token is missing from the JSON body.Pass the request_id from your hidden form field, or the poll token if your integration still uses it.
invalid_request_id400The reference could not be parsed or is empty after normalization.Ensure your frontend submits the Checkify request_id unchanged.
result_not_found404No verification request exists for that reference.Reject the action. The user may have tampered with the hidden field or submitted an old session.
result_expired410The verification completed too long ago to trust for this action.Ask the user to scan again and call verify with the new request_id.
verification_failed403 / 409Mismatch di sito/business, risultato già consumato (HTTP 409) o errore generico di verifica.Nega l’accesso. Ispeziona error.details. Per lacune di claim/campi vedi missing_required_claims e missing_required_fields.
missing_required_claims403Verifica completata ma uno o più required_claims non sono stati approvati.Nega l’accesso. Allinea il request type dell’embed con required_claims, oppure chiedi all’utente di completare la prova corrispondente.
missing_required_fields403Verifica completata ma uno o più required_fields mancavano nel risultato approvato.Nega l’accesso. Richiedi solo i campi effettivamente raccolti dal Pass/embed.
unknown_required_attributes400required_claims o required_fields contiene un nome di attributo sconosciuto.Correggi i nomi di claim/campi prima di riprovare. Vedi la tabella di mapping dei claim in questa pagina.
business_not_operational403The business account is locked, archived, or not operational.Contact the business owner or Checkify support. Do not allow protected actions until the account is active.
validation_errors422The JSON body failed schema validation (HTTP 422).Inspect error.details.validation_errors for field-level messages. Fix request_id, required_claims, or required_fields types before retrying.
rate_limited429Too many verify calls in a short window.Retry with exponential backoff. Verify only at protected actions, not on every page view.
server_error500+Checkify could not complete verification due to a temporary server issue.Retry once or twice, then fail closed and log the incident.

Webhooks and asynchronous verification

Today, server verification is synchronous: your backend verifies the request_id when the user submits the protected action. For checkout, signup, and access-control flows, this is the recommended approach. General-purpose verification webhooks are not required for standard integrations. GoHighLevel and other partner integrations may use separate outbound webhook configuration in the business dashboard.

Common mistakes

Promemoria di sicurezza

Tratta il campo nascosto come un riferimento non attendibile, non come una prova. Verifica sempre la chiave API del tuo sito sul server prima di concedere l'accesso. Utilizzo consume: true per azioni una tantum come la registrazione o la reimpostazione della password.

Next steps