Verify a customer's age

If your platform sells age-restricted goods (alcohol, cannabis, tobacco, gambling, adult content), you need to confirm the customer's age before fulfillment. Frame's CustomerIdentityVerification resource handles this — collect the customer's identity document, verify against the document's date of birth, and surface the result on the customer.

The age-verification concept covers the account-side capability used when account holders (sellers, creators, merchants on your platform) need age-gating. This guide is the customer-side flow: verifying the buyer who's about to purchase from your platform.

When to use this flow

Customer-side identity verification fits the case where you need a one-shot age (and identity) check for a buyer before allowing a purchase. Common shapes:

  • Alcohol or cannabis delivery: verify before the cart can complete.
  • Age-restricted online retail (vape, firearms accessories): gate the checkout button.
  • Gambling deposits: required identity verification at first deposit.
  • Adult content access: prove 18+ at signup.

This is distinct from running KYC on accounts (sellers) — see run KYC for that side.

Prerequisites

RequirementDetails
Frame secret keyFor server-side verification creation.
Customer recordEither create a Customer ahead of time or supply customer fields inline on the verification request.
Document capture flowFrame doesn't host a verification UI today — your application collects the ID photo + selfie and uploads via the documents endpoint.

1. Create the verification

Two shapes depending on whether you have a Customer record yet.

For a new customer — supply identity fields inline:

curl --request POST \
  --url https://api.framepayments.com/v1/customer_identity_verifications \
  --header "Authorization: Bearer $FRAME_SECRET_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "first_name": "Marcia",
    "last_name": "Tyler",
    "date_of_birth": "1985-04-21",
    "email": "marcia.tyler@example.com",
    "phone_number": "+15035550142",
    "ssn": "123-45-6789",
    "address": {
      "line_1": "123 Main St",
      "city": "Portland",
      "state_or_province": "OR",
      "postal_code": "97201",
      "country": "US"
    }
  }'

All identity fields above are required — first_name, last_name, date_of_birth, email, phone_number, ssn, and the full address block. The response includes the verification id — store it against your order or session record.

For an existing customer — use the customer-scoped variant. The customer ID goes in the path; Frame reads the identity fields already on the Customer record, so the body is empty:

curl --request POST \
  --url https://api.framepayments.com/v1/customer_identity_verifications/<customer_id> \
  --header "Authorization: Bearer $FRAME_SECRET_KEY"

If the customer is missing any of the required identity fields above, the verification stays in a non-verified state until you supply them via PATCH /v1/customers/<id>.

2. Upload the identity documents

Verification requires the customer's ID document(s) and a selfie. Your application collects them (camera capture, file upload, whatever fits your UX) and sends them via the documents endpoint. The endpoint accepts front + optional back for the ID document, plus selfie:

curl --request POST \
  --url https://api.framepayments.com/v1/customer_identity_verifications/<id>/upload_documents \
  --header "Authorization: Bearer $FRAME_SECRET_KEY" \
  --form 'front=@/path/to/license-front.jpg' \
  --form 'back=@/path/to/license-back.jpg' \
  --form 'selfie=@/path/to/selfie.jpg'

If your document type is single-sided (e.g., passport), omit back. For multi-document workflows, the endpoint also accepts a documents[] array as an alternative shape.

Frame runs the identity match — comparing the ID document's date of birth, name, and photo against the customer's submitted data and selfie. The verification's status updates asynchronously as the check completes.

3. Read the verification result

Poll the verification or listen for the corresponding webhook:

curl --request GET \
  --url https://api.framepayments.com/v1/customer_identity_verifications/<id> \
  --header "Authorization: Bearer $FRAME_SECRET_KEY"

The response includes verified (boolean) and a verified date_of_birth. Compare the verified DOB against your platform's threshold:

const verification = await getCustomerIdentityVerification(id);

if (!verification.verified) {
  // Identity check didn't pass — could be document quality, name mismatch,
  // or fraud signal. Block the purchase.
  blockCheckout('verification_failed');
  return;
}

const ageMs = Date.now() - new Date(verification.date_of_birth).getTime();
const ageYears = ageMs / (365.25 * 24 * 60 * 60 * 1000);

if (ageYears < 21) {
  blockCheckout('age_under_threshold');
  return;
}

allowCheckout();

Frame exposes Customer#identity_verified? as a server-side helper that reads the customer's latest verification. If you're querying customers via the API, look for the latest_identity_verification association on the customer response — that's where the verified state lands.

4. Gate the purchase on verification result

Wire the verification into your checkout flow. The cleanest pattern: a verification record per checkout session, blocking the "complete purchase" step until the verification resolves.

// On checkout submit
const verification = await createVerification({ customer_id });
await uploadDocuments(verification.id, { id_document, selfie });

// Block checkout until verified
const result = await pollVerification(verification.id);

if (result.verified && getAge(result.date_of_birth) >= 21) {
  await completeOrder();
} else {
  showRejection();
}

Verification typically resolves within seconds for clear documents, longer if manual review is triggered. Don't block the customer's UI on a synchronous poll — show a "verifying..." state and resolve via webhook or background poll.

Once vs every purchase

For repeat customers on the same platform, run verification once and reuse the result. Customer#identity_verified? stays true after a successful verification; subsequent purchases can check the customer record and skip the document upload step.

Some regulatory regimes (especially gambling, some cannabis jurisdictions) require re-verification on a schedule — every N days, every N dollars cumulative, on suspicious-pattern detection. Layer that policy at your application level; Frame doesn't enforce a verification expiry.

Persona is the engine; Frame is the surface

Frame's identity verification runs against Persona under the hood. You don't interact with Persona directly — Frame's API is the integration surface — but the underlying verification mechanics (document type support, accepted ID lists, biometric matching) reflect Persona's capabilities. If your platform needs a specific document type that Frame's verification doesn't accept today, contact support.

Gotchas

Symptom: you ran create_from_customer but the verification stays incomplete. Why: the underlying Customer is missing date_of_birth or one of the required address fields. Fix: update the customer first (PATCH /v1/customers/<id> with the missing fields), then re-trigger the verification. Or fall through to the inline-fields shape.

Symptom: the document upload succeeded but verified came back false with no clear reason. Why: the most common causes are blurry document photo, document type not supported in the customer's region, or a name mismatch between the document and the submitted name. Fix: if you control the UX, surface document-quality guidance (good lighting, fill the frame, no glare) before the upload step. For repeated failures, the customer may need to contact support.

Symptom: a customer who passed verification yesterday can't pass today. Why: most often the customer's ID document expired in the meantime, or the photo they submitted today is significantly degraded. Verification re-runs from scratch on each create; prior success doesn't persist as a free pass. Fix: if you've already verified this customer once, skip the re-verification — read the existing latest_identity_verification on the customer record. Only re-verify when your policy requires it (jurisdiction, time-based, suspicious activity).

Symptom: you want to verify a customer who's under 18. Why: Frame's Customer model rejects DOB values that don't meet the minimum-age threshold (18+) at creation time. Fix: this is platform-policy enforcement at the API surface. If your platform has a legitimate under-18 use case, contact Frame support.

Next steps