Age assurance is a software dependency, not a runtime toggle. The native SDKs are linked into your binary at build time, so enabling it in Despia does nothing until you ship a new build with an increased version code. It also needs configuration in the Apple and Google accounts you own. Until all three are done, every call answers
status: "unavailable", which is safe to ship but is not an answer. See Enabling age assurance below.Installation
- Bundle
- CDN
How it works
Assignwindow.onAgeAssuranceResult, then fire ageassurance://check. The operating system shows its own consent sheet the first time and the verdict arrives at your callback. Nothing throws and nothing rejects: every outcome, including every failure, arrives as a verdict you read the same way.
gates is a comma-separated list of up to three ages you want answered, and it defaults to 13,16,18. Ask for the ages you actually gate on, since anything else comes back as unknown rather than as a guess. id is echoed back on the verdict as requestId so you can tell two requests apart, and accepts letters, digits, _, . and - up to 128 characters.
Reading the verdict
Branch onstatus first. It tells you what kind of answer you got, and ok is a shorthand for the two statuses where you learned nothing about the user.
The user shared a band. Carries
ageLower and ageUpper as whole years, either of which can be null for an open-ended band, plus assurance and a computed gates map. This is the only status where gates can answer anything other than null.object
The user saw the sheet and chose not to share.
ok is true because this is a real answer, not a failure. Every gate is null and there is no band. Fall back to your own gating.object
Android only. Google holds no usable signal until the user completes verification with Play. Fire
ageassurance://resolve to send them into that flow.object
iOS only. Apple reports this user is not subject to age checks at all, which is knowledge rather than absence. Treat it as an adult account unless your own policy says otherwise.
No provider could answer.
ok is false and error.code says why: os_unsupported, not_configured, no_play_services, no_result, or a Play condition this install cannot recover from. Use your own gating.object
The call itself failed.
ok is false. error.code is stable and machine-readable, error.message is advisory text for your logs. Common codes are invalid_gates, too_many_gates, unsupported_action, network_error, and timeout.Awaiting a verdict instead of a callback
Name the global you want read back and the call resolves once the verdict lands there. Every answer is written towindow.ageAssuranceVerdict, so that is the key to watch.
ageassurance://last and ageassurance://forget. The watch gives up after 30 seconds, and a check on a cold cache waits on a human reading a system consent sheet, which can take longer than that. Anything that prompts belongs on the callback above, which fires whenever the answer arrives no matter how long the user takes.
Fire one command at a time and wait for the answer before issuing the next.
Reading a gate without under-gating a minor
Each key ingates is one of three values, and the third one is the point of the whole design. true means the band’s floor clears that age. false means its ceiling falls below it. null means the age falls inside the band and the operating system genuinely did not answer that question.
=== true before you unlock anything, and never treat null as false or as true. A user in a 13 to 16 band asked about 15 gets null, and reading that as “not old enough” over-restricts them while reading it as “old enough” hands adult content to a minor. That second mistake is the one that costs an app its store listing. Keys serialize as strings, so use verdict.gates["18"], and note that a gate falling between two of your configured Android bands can only ever answer null.
Knowing how the age was established
assurance tells you how much weight the answer carries, so you can require a stronger signal for the things that need one.
Checking availability before you prompt
Branch on what the build reports, never on the operating system version or the user agent. These are plain globals, so reading them costs nothing and never prompts. The SDK proxies property reads through towindow, so despia.ageAssuranceAvailable and window.ageAssuranceAvailable are the same value.
ageAssuranceFeatures names the provider-backed hosts, so forget is not in it: it works on every build, with or without a provider.
These globals exist only in your top-level page. An <iframe> never receives them, and an ageassurance:// call from one is refused without prompting. A framed widget that needs the verdict gets it from the top-level page through postMessage.
Clearing the verdict on logout
The verdict is held for the life of the app process, which is what stops a page reload prompting the user again. Nothing expires it, so on logout or an account switch, clear it yourself.ageAssuranceVerdict back to null. It works with or without a provider present and it never prompts. Without it, the next person to sign in on that device can read the previous user’s age range.
Handling verification on Android
Google can answerverification_required, meaning the user has to complete verification with Play before any signal exists. Firing ageassurance://resolve re-runs the flow and lets Play present its own verification screens.
resolve is absent from ageAssuranceFeatures and firing it answers error with unsupported_action, so test ageAssuranceFeatures before you offer the option. The callback above re-enters on the second verdict, so guard on status to avoid looping.
Enabling age assurance
Age assurance links native SDKs into your app, so it is enabled at build time rather than at runtime. The toggle, the store-side configuration, and a fresh build are all required before any real signal arrives.1
Enable the addon in Despia
Open your app in the Despia Editor, go to App, then Addons, and turn on Age Assurance.
2
Enable the Apple capability
Open Certificates, Identifiers and Profiles, select the identifier matching your app’s bundle ID, and enable Declared Age Range under Capabilities. If it is not in the list, open the Capability Requests tab on the same identifier and request it.
3
Regenerate your provisioning profile
Open Profiles, edit the profile your app builds with, save it, and download the result. A profile created before the capability existed does not carry it, and the build switches the feature off rather than failing to sign.
4
Accept the Play Age Signals terms
In Google Play Console, open your app, then Policy and programs, then App content. Find Age signals and accept the terms.
5
Choose your Android age bands
On the same Age signals page, set the minimum ages your app needs, up to three, for example 13, 16 and 18. Match them to the ages you actually gate on: a gate that falls between two configured bands can only ever answer
null.6
Rebuild with an increased version code
Increase your version code, then trigger a fresh build in Despia and ship it. The native SDKs are compiled into the binary, so the addon has no effect on any build made before you turned it on.
Coverage on each platform
Below iOS 26.2 there is no such API to call, so
available is false with reason: "os_unsupported" and your own gating has to carry the user. The iOS Simulator always answers unavailable, so testing the real flow needs a physical device on iOS 26.2 or later. Android reaches effectively every device you ship to, which makes it the platform where this feature does most of its work today.
Because Apple shapes its band from the ages you pass at call time and Google returns bands you configured in Play Console, the same user can produce different ageLower and ageUpper values on each platform. The gates map is the part that means the same thing everywhere, which is why it is what you should branch on.
Verifying the result on your server
Everything your web app receives is client-side and can be forged by anyone who can run code in the page. Treat the verdict as a signal that shapes the experience, never as proof.Resources
NPM Package
despia-native