Checkify
开发人员文档

在后端验证

浏览器嵌入完成后,您的服务器必须使用站点API密钥验证结果,然后才能允许注册、结帐或任何受保护的操作。

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。创建站点API key

在您的业务仪表板中,打开 开发者 并为您正在集成的站点创建一个站点 API 密钥。仅将其存储在服务器环境变量中 - 切勿将其发送到浏览器。

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.
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_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

Besides required_claims, you may require specific approved identity fields (for example country or age band) when your integration collected them. Pass field names in required_fields — if any are missing from the approved result, verify returns verification_failed with 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.

响应处理

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

The signed_result object lets your backend keep a tamper-evident audit record that Checkify approved the required claim at verification time. Most integrations only need approved_claims. Regulated or high-risk businesses may also store signed_result for audit. Do not store more personal information than necessary.

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
}
字段含义
successtrue 验证完成且要求匹配时
statuscompletedpending
approved_claims声明Checkify已批准,例如 human_verified: true
signed_result用于审计跟踪的可选签名负载

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 / 409Verification finished but did not meet your required claims/fields, belongs to another site, or was already consumed.Deny access. Inspect error.details for missing_claims, missing_fields, or reason.
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

安全提醒

将隐藏字段视为不可信参考,而不是证据。在授予访问权限之前,请务必使用服务器上的站点 API 密钥进行验证。使用 consume: true 进行注册或密码重置等一次性操作。

Next steps