CAIRLDocs
Integration

Drop-in Checkbox (in development)

Design preview of the CAIRL Checkbox — a privacy-first age and identity verification widget. The script is not yet served; use the OAuth quickstart to integrate today.

⚠️ Not yet available — this is a design preview

https://cairl.app/v1/checkbox.js is not served yet. The snippets on this page describe the intended integration contract, not a working one: pasting them into a page today loads nothing.

Two gate-removal blockers remain — partner-backend-owned PKCE, and the hosted-modal completion emitter — so redirect mode cannot retrieve claims and iframe mode never resolves. pk_live issuance is also not yet activated.

To integrate identity verification today, use the OAuth quickstart — it is live and supported.

This page stays published so the contract can be reviewed before it ships. It becomes a working guide when the widget is served.

What you'll build

The CAIRL Checkbox is a copy-paste age and identity verification widget for any website — WordPress, Shopify, or plain HTML. No npm, no React, no account.

By the end of this guide your page will:

  1. Render a verification checkbox where you drop a <div>.
  2. Hand the visitor to CAIRL when they tap it (full redirect, or an in-page iframe overlay).
  3. Receive an OAuth authorization code on your callback and exchange it, server-side, for boolean claims.

You do not receive raw identity data. You exchange the code for boolean claims (e.g. claims.age_21_plus: true) under a pseudonymous, site-scoped sub. No name, no document, no selfie — the document and the biometrics stay on CAIRL’s side of the consent boundary.


Prerequisites

ItemHow to get it
Publishable key (pk_*)A pk_sandbox_* key for browser testing. Live keys (pk_live_*) activate at launch.
Registered redirect URIRegistered on /home/f/{slug}/connect. The resolver only accepts registered callback URLs.
Sandbox (optional)If /home/developer/sandbox is available in your environment, it renders a synthetic run with no key.

The publishable key is browser-safe by design — it carries no policy of its own and resolves server-side to your account's claim authorization. It is the only credential the snippet needs. Never put a cairl_* secret key in a page.

Where available, sandbox recipes render the checkbox experience and synthetic result before you create a pk_* key. They do not create a billable Verified Access Event, balance debit, enrollment, or production claim.


Step 1 — Add the checkbox (redirect mode)

Drop these two lines anywhere on your page. Swap pk_sandbox_your_key_here for your key and data-cairl-redirect for your callback URL.

<!-- 1. The checkbox renders here -->
<div
  data-cairl-checkbox
  data-cairl-key="pk_sandbox_your_key_here"
  data-cairl-redirect="https://yourstore.example/age-callback"
  data-cairl-profile="age-gate-21"
  data-cairl-mode="redirect"
></div>

<!-- 2. Load the widget once -->
<!-- NOT YET SERVED — this URL 404s today. See the notice at the top of this page. -->
<script src="https://cairl.app/v1/checkbox.js" async></script>

That's the whole front-end. The script finds every [data-cairl-checkbox] element, renders the checkbox, and starts the flow on click.

Attributes

AttributeRequiredDescription
data-cairl-checkboxyesMarker — its presence opts the element in.
data-cairl-keyyesYour publishable key (pk_sandbox_* / pk_live_*).
data-cairl-redirectyesYour callback URL. Must be a registered redirect URI.
data-cairl-profilenoCopy hint, e.g. age-gate-21, age-gate-18, trust-gate-standard, liveness-only. The actual claims are bound to your key, not chosen in the browser.
data-cairl-modenoredirect (default) or iframe.
data-cairl-trust-badgenofalse to hide the CAIRL microbrand attribution.

Step 2 — Handle the callback

When the flow completes, CAIRL redirects the visitor back to your data-cairl-redirect URL with an OAuth 2.0 authorization code and your state — exactly the Authorization Code + PKCE callback CAIRL's hosted flow uses everywhere:

https://yourstore.example/age-callback?code=<code>&state=<state>

There is no token in the URL. The code is a short-lived, single-use handle; you exchange it server-to-server for the verified claims. Do this on your backend — the client_secret and PKCE code_verifier never touch the browser.

PKCE is your backend's job. Before the flow starts, your backend mints a PKCE pair (a random code_verifier and its S256 code_challenge) and a random state, and keeps the verifier server-side keyed to that state. The challenge is bound to the authorization request; the verifier proves, at exchange time, that the same backend that started the flow is finishing it.

// 1. Validate state (CSRF) against the value you stored when the flow started,
//    then look up the matching PKCE code_verifier for this state.
export async function handleAgeCallback(req, res) {
  const { code, state } = req.query;
  const pending = await consumePendingFlow(state); // your store; null if unknown
  if (!pending) {
    return res.status(400).send("Invalid or expired state");
  }

  // 2. Exchange the code (+ the PKCE verifier) for an access token.
  const tokenRes = await fetch("https://cairl.app/api/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      redirect_uri: "https://yourstore.example/age-callback", // must match exactly
      client_id: process.env.CAIRL_CLIENT_ID,
      client_secret: process.env.CAIRL_CLIENT_SECRET, // server-side only
      code_verifier: pending.codeVerifier,
    }),
  });
  if (!tokenRes.ok) {
    return res.status(400).send("Token exchange failed");
  }
  const { access_token } = await tokenRes.json();

  // 3. Read the verified claims from userinfo with the access token.
  const claimsRes = await fetch("https://cairl.app/api/oauth/userinfo", {
    headers: { Authorization: `Bearer ${access_token}` },
  });
  const { sub, claims } = await claimsRes.json();

  // sub is a pseudonymous, site-scoped identifier — not a global user id.
  // The claims a key may request are bound to the key (scope `age:21+`
  // returns `claims.age_21_plus`).
  if (claims.age_21_plus === true) {
    // allow
  } else {
    // block or offer an alternative
  }
}

Claim values are true (passed), false (failed), or null (inconclusive — the visitor could retry, you decide the fallback). The authorization code is single-use and short-lived, so the exchange itself is the enforcement point; see OAuth and OIDC for the full token and userinfo contract and Verification sessions for session introspection.


Step 3 (optional) — Iframe mode

Iframe mode keeps the visitor on your page: the verification opens in a CAIRL-hosted overlay. Your page receives the outcome via DOM events instead of a redirect.

<div
  data-cairl-checkbox
  data-cairl-key="pk_sandbox_your_key_here"
  data-cairl-redirect="https://yourstore.example/age-callback"
  data-cairl-profile="age-gate-21"
  data-cairl-mode="iframe"
></div>
<!-- NOT YET SERVED — this URL 404s today. See the notice at the top of this page. -->
<script src="https://cairl.app/v1/checkbox.js" async></script>

<script>
  document.addEventListener("cairl:complete", function (e) {
    // e.detail = { canonicalClaimIds, sessionRef, modeType, profileLabel }
    console.log("verified", e.detail);
  });
  document.addEventListener("cairl:error", function (e) {
    console.warn("verification error", e.detail);
  });
</script>

The overlay is a sandboxed, CAIRL-origin iframe. CAIRL still owns the consent and capture surface end-to-end — the overlay cannot be re-skinned to obscure the consent disclosures, and the result channel is bound per-launch (message source + a per-launch nonce + state, with version/surface/type discriminators) so a result can't be forged or replayed from the host page. (Origin is intentionally not part of the binding: a sandboxed frame presents an opaque origin, so the parent gates on event.source reference-equality plus the nonce/state echo instead.)

Confirm on your server. The cairl:complete event is a convenient UX signal — it carries no proof and must not gate access on its own. Confirm server-side before granting access: either the code exchange (Step 2) or a webhook keyed to the session is the source of truth.


Programmatic API

If you render checkboxes dynamically, skip the auto-scan and call the widget's global (part of the same not-yet-served script):

window.Cairl.checkbox({
  el: document.querySelector("#age-gate"),
  key: "pk_sandbox_your_key_here",
  redirect: "https://yourstore.example/age-callback",
  mode: "iframe",
  profile: "age-gate-21",
  onComplete: (detail) => console.log("verified", detail),
  onError: (detail) => console.warn(detail),
});

// Re-scan after injecting new [data-cairl-checkbox] nodes:
window.Cairl.mount();

Sandbox → live

pk_sandbox_* keys return deterministic fixtures — they do not call a real biometric provider, create a user, or bill. They are safe in public docs, demos, and automated tests. Build and ship your entire integration against sandbox with zero human in the loop.

Live publishable keys (pk_live_*) issue when production verification opens. The integration contract does not change — you swap the key prefix and your checkbox is live.

Go live self-serve

  1. Create a business facet (no sales call).
  2. Add funds at /home/f/{slug}/billing$50 minimum load.
  3. Issue live API keys on /home/f/{slug}/keys and swap pk_sandbox_* for pk_live_*.

See metered pricing for per-check costs (~$0.10 typical age check — a fraction of typical document-upload identity verification).


Privacy & compliance notes

  • Pseudonymous by construction. The sub you receive is a site-scoped pseudonym designed to reduce cross-site linkability; it is not the user's global CAIRL identifier and must still be treated as personal data.
  • Raw material stays out of the result. The Checkbox response does not send identity documents or biometric captures to your application. CAIRL handles source material under its applicable retention and deletion controls.
  • Hosted capture boundary. The CAIRL-hosted flow returns a derived result rather than biometric bytes. Your business remains responsible for its integration choices and applicable privacy and biometric-law obligations.
  • You avoid holding raw identity material. You receive derived booleans rather than raw documents, names, or biometric material. Those assertions are still personal data and must be protected and retained appropriately.

See the live demo to watch the widget run, and Errors for the full error-code reference.

On this page