Skip to content

Setup guide

Add age checks to your website

Start by choosing how your age check looks and works. Then connect it to your website, with help from your website developer if needed. This guide covers both parts.

Start here

Add your website in Carded, then choose how its age check window looks and works. This window is called a widget in the technical guide below.

In your Carded account

Make the check yours

  1. Add your website to your Carded account.
  2. Add your business name, colours and wording.
  3. Choose the minimum age and how people prove it: photo ID, face scan or both.
  4. Set the face scan safety margin, decide on a selfie with ID, and choose whether customers can carry on from their phone.
Open my websites

On your website

Connect the customer journey

This part involves adding website code and confirming results on your server. If someone manages your website, give them the checklist below and the technical guide that follows.

What do widget, session and API mean?
Widget
The branded window where your customers complete an age check.
Session
The record of one customer's check, from starting it to receiving the result.
API
The connection your website's server uses to request and confirm checks with Carded.

For your website developer

Choose an integration below. Both options use the server API to confirm the final result.

How the website connection works

Choose a popup on your website or a separate Carded page that sends the customer back after their check. Each check creates a session. Attach your own customer reference to it, then confirm the result through the server API before allowing the customer to continue.

  1. Step 1

    Start the check

    Open the widget or redirect to a hosted check with your customer reference.

  2. Step 2

    Verify the customer

    Assess the ID, age and document expiry, with an optional selfie match.

  3. Step 3

    Confirm the result

    Use the session ID and your secret key to retrieve the outcome from your server.

Carded.verify() opens the verification over your page, so the customer stays on your website. It resolves with the result when it closes.

HTML
<!-- 1. Add this once, just before </body> -->
<script src="https://your-carded-address/widget.js" async></script>

<!-- 2. Start a check whenever someone needs one -->
<button id="verify-age">Verify my age</button>
<script>
  document.getElementById("verify-age").addEventListener("click", async () => {
    const result = await Carded.verify({
      siteKey: "pk_YOUR_PUBLIC_KEY_HERE_000",
      reference: "user-42", // your own ID for this person (optional)
    });

    if (result.passed) {
      // Send result.sessionId to your server and confirm it there
    } else {
      // result.status is "failed" or "cancelled", result.reason says why
    }
  });
</script>

Resolved result

Result
{
  "sessionId": "8f0c2a5e-6b1d-4c2e-9a77-1f6b3c9d2e10",
  "status": "failed",
  "passed": false,
  "reason": "underage",
  "facePhoto": null
}
OptionTypeDescription
siteKeystringRequired. Your widget's public key (pk_...).
referencestringOptional. Your customer reference, up to 200 characters. Included in the API response.
session{ id, token }Optional. Resume an API session created by your server to preserve its assigned reference.
onResultfunctionOptional. Called when a final result is available, before the popup closes.
onCompletefunctionOptional. Called when the popup closes, with the same result the promise resolves to.
onErrorfunctionOptional. Called if the widget cannot start, for example when the website origin is not allowed.

To launch the widget using HTML attributes, add data-carded-verify and data-site-key to an element. Clicking it opens the check and dispatches a carded:complete event with the result.

Face scan checks also include the face photo as a JPEG data URL in facePhoto (popup) and face_photo (server API, including after a full page return). It is kept for 24 hours, then deleted. It is null for photo ID checks. Face photos are biometric data, so cover them in your privacy notice.

Option 2: a separate check during sign-up

Use a hosted redirect when verification is part of registration. Your server creates the session and assigns its reference. Redirect the customer to the returned link; they return to your return_url with carded_session and carded_status parameters. The return address must be included in the widget's allowed return URLs.

Your server (Node.js)
// 1. After someone signs up on your site, create a check for them
const res = await fetch("https://your-carded-address/api/public/v1/sessions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CARDED_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    reference: user.id,
    return_url: "https://your-site.co.uk/signup/verified",
  }),
});
const session = await res.json();

// 2. Remember session.id against the user, then send them to be checked
await db.users.update(user.id, { cardedSessionId: session.id });
return redirect(session.url);

// 3. They land back on your return_url with
//    ?carded_session=...&carded_status=passed (or failed / cancelled)
//    Look the session up from your server before you trust it.

Face scans and phone handover

Choose photo ID, face scan or both in the widget settings. A face scan estimates age from one live photo and passes only when the low estimate is at least your minimum age plus the safety margin. Face scan results include the face photo for 24 hours.

On a computer, customers can scan a QR code to take photos on their phone. It is the same session, and the result is delivered on the computer where it started, so you need no extra code. Switch this off with "Let customers use their phone".

Confirm every result on your server

Browser results can inform the interface, but must not authorise access. Retrieve the session from your server using the secret key, confirm the verification passed and ensure its reference matches the expected customer.

Node.js
// On your server. Never put your secret key in the browser.
const res = await fetch(`https://your-carded-address/api/public/v1/sessions/${sessionId}`, {
  headers: { Authorization: `Bearer ${process.env.CARDED_SECRET_KEY}` },
});
const session = await res.json();

if (session.status === "passed" && session.reference === user.id) {
  // Mark the account as age verified
}
Terminal
curl https://your-carded-address/api/public/v1/sessions/SESSION_ID \
  -H "Authorization: Bearer $CARDED_SECRET_KEY"

API reference

Send your secret key as Authorization: Bearer sk_.... The API is intended for server-to-server requests. Keep secret keys out of browser code.

POST
/api/public/v1/sessions

Creates a verification session. Optional JSON body: reference (string, up to 200 characters) and return_url (must be on your allowed list). Returns the session plus url (the hosted check) and client_token (for Carded.verify({ session })).

GET
/api/public/v1/sessions/:id

Returns the session status, outcome and metadata. Identity document fields are excluded.

Response
{
  "object": "verification_session",
  "id": "8f0c2a5e-6b1d-4c2e-9a77-1f6b3c9d2e10",
  "status": "passed",
  "passed": true,
  "reason": null,
  "reference": "user-42",
  "min_age": 18,
  "attempts": 1,
  "source": "widget",
  "created_at": "2026-11-06T17:40:12.000Z",
  "completed_at": "2026-11-06T17:41:03.000Z",
  "expires_at": "2026-11-06T18:10:12.000Z",
  "face_photo": null
}

Errors use { "error": { "code", "message" } } with an HTTP status, such as 401 for an invalid key or 404 for an unknown session.

What each session status means

StatusDescription
passedThe check met the configured age and verification requirements.
failedThe check did not pass. The reason code explains the outcome.
cancelledThe customer closed the verification before completion.
pendingThe session has started and has no final result.
expiredThe session was not completed within its 30-minute validity period.

Failure reasons

Retryable outcomes allow another attempt, up to the limit configured for the widget. A final failure response includes the reason from the last attempt.

CodeMeaningCan retry
underageThe date of birth on the ID is below the minimum age set for this widget.No
document_expiredThe photo ID has passed its expiry date.
document_unreadableThe date of birth or photo on the ID could not be read clearly.
document_not_acceptedThe image was not an accepted photo ID such as a passport, driving licence or national ID card.
document_not_originalThe ID appeared to be a photocopy, a printout or a picture of a screen.
face_mismatchThe selfie did not appear to match the photo on the ID.
selfie_unclearThe selfie was missing a clear, single face.
suspected_fraudThe ID or selfie showed signs of tampering or spoofing.No
face_unclearThe face scan did not show one clear, live face.
age_estimate_too_closeThe face scan could not confidently show the person is over the minimum age.No

Before you go live

  • Add each website origin and return URL to the widget's allowlists.
  • Keep your secret key on your server only, in an environment variable.
  • Confirm each result from your server and match its reference to the expected customer.
  • Create sessions on your server when the reference must be assigned by your application.
  • Use the published Carded address in your production integration.