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
- Add your website to your Carded account.
- Add your business name, colours and wording.
- Choose the minimum age and how people prove it: photo ID, face scan or both.
- Set the face scan safety margin, decide on a selfie with ID, and choose whether customers can carry on from their phone.
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.
- Step 1
Start the check
Open the widget or redirect to a hosted check with your customer reference.
- Step 2
Verify the customer
Assess the ID, age and document expiry, with an optional selfie match.
- Step 3
Confirm the result
Use the session ID and your secret key to retrieve the outcome from your server.
Option 1: a popup on your website
Carded.verify() opens the verification over your page, so the customer stays on your website. It resolves with the result when it closes.
<!-- 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
{
"sessionId": "8f0c2a5e-6b1d-4c2e-9a77-1f6b3c9d2e10",
"status": "failed",
"passed": false,
"reason": "underage",
"facePhoto": null
}| Option | Type | Description |
|---|---|---|
| siteKey | string | Required. Your widget's public key (pk_...). |
| reference | string | Optional. 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. |
| onResult | function | Optional. Called when a final result is available, before the popup closes. |
| onComplete | function | Optional. Called when the popup closes, with the same result the promise resolves to. |
| onError | function | Optional. 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.
// 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.
// 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
}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.
/api/public/v1/sessionsCreates 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 })).
/api/public/v1/sessions/:idReturns the session status, outcome and metadata. Identity document fields are excluded.
{
"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
| Status | Description |
|---|---|
| passed | The check met the configured age and verification requirements. |
| failed | The check did not pass. The reason code explains the outcome. |
| cancelled | The customer closed the verification before completion. |
| pending | The session has started and has no final result. |
| expired | The 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.
| Code | Meaning | Can retry |
|---|---|---|
| underage | The date of birth on the ID is below the minimum age set for this widget. | No |
| document_expired | The photo ID has passed its expiry date. | |
| document_unreadable | The date of birth or photo on the ID could not be read clearly. | |
| document_not_accepted | The image was not an accepted photo ID such as a passport, driving licence or national ID card. | |
| document_not_original | The ID appeared to be a photocopy, a printout or a picture of a screen. | |
| face_mismatch | The selfie did not appear to match the photo on the ID. | |
| selfie_unclear | The selfie was missing a clear, single face. | |
| suspected_fraud | The ID or selfie showed signs of tampering or spoofing. | No |
| face_unclear | The face scan did not show one clear, live face. | |
| age_estimate_too_close | The 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.