CheckboxDocs

Checkbox Elements

A drop-in web component (and React wrapper) that shows the locked, compliant automatic-renewal disclosure above your pay button with an unchecked consent checkbox, and records each consent as evidence.

<checkbox-disclosure> renders the disclosure for your terms — price, interval, trial, that it renews until cancelled, how to cancel — in a bordered box at body size or larger, directly above your pay button, with an unchecked, required consent checkbox. It keeps the pay button disabled until the shopper ticks the box, and records each consent with Checkbox, so you get a cons_… id to store with the subscription. The wording is the same locked text the rules engine verifies everywhere else.

Install

  1. Get the pinned script tag with Subresource Integrity from the manifest (or from createDisclosure, below):
curl https://app.checkbox.site/v1/elements/manifest.json
  1. Put the script tag in <head> and the element inside your sign-up form, directly above the pay button:
HTML
<script src="https://app.checkbox.site/v1/elements/1.0.0/elements.js"
        integrity="sha384-…" crossorigin="anonymous" defer></script>

<form action="/subscribe" method="post">
  <checkbox-disclosure
    publishable-key="pk_test_..."
    merchant-name="Joybox" amount="1999" currency="usd" interval="month" trial-days="14"
    cancel-url="https://joybox.example/account/cancel"
    customer-reference="cs_test_a1b2c3">
  </checkbox-disclosure>
  <button type="submit">Start free trial</button>
</form>
  1. Your server receives auto_renew_consent (the label text) and checkbox_consent_id (cons_…) with the form. Store the id with the subscription — for example metadata: { checkbox_consent: 'cons_…' } on the Stripe Checkout Session — or set customer_reference later with updateConsent.

/v1/elements.js always serves the current version (cached for 5 minutes), handy while prototyping. In production pin /v1/elements/<version>/elements.js with its integrity; released versions never change.

React

React
import { CheckboxDisclosure } from '@subcomply/elements/react';

export function Consent({ sessionId, onConsent }: { sessionId: string; onConsent: (id: string) => void }) {
  return (
    <CheckboxDisclosure
      publishableKey={process.env.NEXT_PUBLIC_CHECKBOX_PUBLISHABLE_KEY!}
      terms={{ merchant_name: 'Joybox', amount: 1999, currency: 'usd', interval: 'month', trial_days: 14,
               cancel_url: 'https://joybox.example/account/cancel' }}
      customerReference={sessionId}
      onConsent={(consent) => onConsent(consent.id)}
    />
  );
}

In single-page apps that submit with fetch, use recordConsent="manual" and call await ref.current.recordConsent() before creating the subscription. (The React entry is published with the SDK packages; see the pre-release note.)

Server-issued disclosures

createDisclosure derives the terms from the site's billing connection (or the terms you pass), generates the locked wording, runs the rules engine on your checkout with the element installed, and returns the markup to paste:

curl https://app.checkbox.site/v1/disclosures \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
    "terms": {
      "merchant_name": "Joybox",
      "amount": 1999,
      "currency": "usd",
      "interval": "month",
      "trial_days": 14,
      "cancel_url": "https://joybox.example/account/cancel"
    }
  }'

With disclosure="disc_…" on the element, it loads its text from Checkbox (publishable key, Origin-checked). Keep the terms attributes as well: they are the fallback if Checkbox cannot be reached. Without either, the element shows "Subscription terms unavailable" and keeps the pay button disabled.

Attributes

Attribute
publishable-keypk_test_… / pk_live_…. Without it the element still works but records nothing
merchant-name, amount (minor units), currency, interval (day, week, month, year), interval-count, trial-days, cancel-url (https), cancel-timing (period_end or immediately)Your terms
disclosuredisc_… from createDisclosure
sitesite_…; optional (resolved from the page's domain), needed on localhost when you have several sites
submitCSS selector of the pay button(s) to gate; default: the form's submit buttons
formid of the form when the element is outside it
customer-referenceYour id for the subscriber (a Stripe session or customer id). Emails are refused
record-consentsubmit (default), change (when ticked) or manual
placementauto moves the element directly above the pay button
name, consent-id-nameForm field names (default auto_renew_consent, checkbox_consent_id)
api-baseDefaults to the origin the script was loaded from

Events (they bubble): checkbox:ready, checkbox:change { consented }, checkbox:consent { id, consent }, checkbox:guardrail { violations, messages, fallbackApplied }, checkbox:error { code, message }. Methods: isConsented(), recordConsent({ customerReference?, metadata? }), consentId, checkGuardRails(), refresh().

A Checkbox outage never blocks a sale: if recording fails or takes over 6 seconds, the form submits without the id and checkbox:error fires.

Guard rails

Theme it with CSS custom properties (--checkbox-font-size, --checkbox-text-color, --checkbox-border-color, --checkbox-background, --checkbox-accent-color and others). After rendering, and on resize, the element measures what the shopper actually sees with the rules engine's own thresholds — font size at least the page's body size, WCAG AA contrast, visible, set off by a border, above the pay button and within 250px of it. If your theme or page CSS breaks size, contrast or the border, it restyles itself with safe inline styles, logs a warning and fires checkbox:guardrail. A checkbox ticked by a script is not consent: the element unticks it and reports prechecked.

Content-Security-Policy

The element uses no eval, no inline styles or <style> elements and no innerHTML. Allow script-src https://app.checkbox.site and connect-src https://app.checkbox.site.

Consent records

Each consent stores the disclosure text and label shown and their hashes, whether they match the locked wording, the terms, the checkbox state (checked, initially checked, required), server time, the page URL, Origin, a hash of the User-Agent, the element version and its on-screen measurement.

The publishable key is public, so a browser consent is a client claim: the customer-reference the page sends is kept as client_reference (unverified), never as customer_reference. Bind the consent to the real subscription from your server: the form carries checkbox_consent_id; call updateConsent with a secret key and the subscription id (or record the consent from your server with createConsent and a secret key). Browser consents are limited per site and day; corroborating page captures are stored for the first 50 a day. Origins match the site's own host and its subdomains; on shared hosts (*.vercel.app, *.netlify.app, *.onrender.com, *.github.io, …) each customer's subdomain is its own site. The full record goes into the site's evidence chain, and consent.created is sent to your webhooks. List them with listConsents.

Checkbox is compliance software, not a law firm; nothing in these docs is legal advice (details). For AI agents: every page is available as Markdown at the same URL plus .md; the index is /llms.txt.