Consenti

COPPA Compliance Guide

ℹ️Compliance group: general-privacy-consent — full-flexibility mode; configure your profile to show age-gated consent for children under 13. Use compliance: { type: 'general-privacy-consent' } in your ConsentiSetup config.

COPPA (Children's Online Privacy Protection Act) applies to websites and online services directed at children under 13 in the United States.

Configuration

Age gate is a per-profile, per-locale dashboard setting, not acreateConsenti()/ConsentiSetup()config option — one global age doesn't fit every jurisdiction a multi-region deployment serves. In the dashboard's Profile Editor:

  1. Step 1 → enable "Age gate", set minimum age to 13, check "Require parental consent".
  2. Main Banner content step → author the age-gate modal's heading, body text, and Yes/No button labels for each locale you support.

The widget reads this straight off the resolved profile (profile.ageGate/profile.ageGateModal) — no separate widget-side config needed. For a standalone/local profile (registerProfile()), set ageGate/ageGateModal directly on the EmbeddedProfile/EmbeddedTranslations you register instead.

How it works

When the active profile's ageGate.enabled: true, the widget shows a Yes/No age-confirmation prompt before anything else — banner, GPC, CCPA opt-out all wait behind it on first visit:

  1. Confirmed (visitor is minimumAge or older) → normal banner/GPC/CCPA flow proceeds; every consent submission from then on carries ageVerified: true.
  2. Declined, requireParentalConsent: false → a deny-all consent is submitted immediately (mandatory/strictly-necessary cookies still granted), ageVerified: false, no banner shown.
  3. Declined, requireParentalConsent: true → same deny-all submission, plus the widget requests a parentalConsentToken from the backend (signed with compliance.dataSigningHash, auto-generated if not set) and fires a consenti:parentalConsentRequired event carrying it — see UI Events.
  4. The age_verified and parental_consent_token columns are stored on the consent record either way.

API fields

json
{
  "visitorId": "uuid",
  "consentJson": { "analytics": "denied" },
  "ageVerified": false,
  "parentalConsentToken": "pcon_..."
}

Parental consent flow

Consenti mints a stateless, signed parentalConsentToken and emits it — no server-side persistence of the token itself, and no email-sending infrastructure in this zero-runtime-dependency package. Both the request and the resolve step are plumbing for your own out-of-band verification process, for example:

  1. Widget declines → calls POST /consent/:visitorId/parental-consent-request, fires consenti:parentalConsentRequired with the returned token
  2. Your eventBus.on('consent.parentalConsentRequired', ...) listener sends the parent an email with a verification link containing the token
  3. Parent clicks the link → your backend verifies them however you choose
  4. Once verified, call POST /consent/parental-consent-resolve with { token } — either from the parent's page (via resolveParentalConsent() from @consenti/ui) or server-side. Consenti emits consent.parentalConsentGranted; your own eventBuslistener decides what "granted" means for stored consent (e.g. call PUT /consent/:visitorIdyourself) — Consenti doesn't update the record automatically.
⚠️Token replay isn't prevented — there's no persistence to mark a token "used," an accepted tradeoff of staying fully stateless. compliance.dataSigningHashis auto-generated in memory if you don't set it, so tokens are always signed — but with an ephemeral, unpersisted key unless you set it explicitly, meaning a server restart between issuing and resolving a token invalidates it. Set compliance.dataSigningHash before relying on this in production.

Dashboard filtering

In the admin dashboard, consent records with age_verified: true are flagged so compliance officers can audit them separately.

🚨COPPA applies to operators, not technology vendors. You are responsible for implementing the full parental consent flow. Consenti provides the infrastructure; your legal team determines whether COPPA applies to your service.
⚠️For services not directed at children: implement age screening. Redirect users who indicate they are under 13 away from your service entirely (do not simply deny consent). This is a legal requirement, not a UX choice.