Your age-signal integration probably compiles, passes review, and does nothing
If you ship an Android app with ads, someone has probably asked you in the last few months whether you handle app-store age signals. If you have already done the work, this is about the specific way that work tends to fail silently.
And if you have not started, the timeline is not the one most people think.
The part that fails quietly
Google Play and, since iOS 26, Apple will tell your app roughly how old the person using it is. The obligation that follows is not to receive that signal. It is to act on it — which in practice means telling every advertising and analytics SDK in your build to stop profiling this user.
Each SDK has its own switch:
AdSettings.setMixedAudience(true); // Meta Audience Network
IronSource.setMetaData("is_child_directed", "true"); // ironSource / LevelPlay
MetaData md = new MetaData(context); // Unity Ads
md.set("user.nonbehavioral", true); md.commit();
Every one of these has the same requirement: it must be set before that SDK initialises. Set it afterwards and the call succeeds, returns normally, logs nothing, and has no effect on the session.
That is what makes it dangerous. The code is present. It is in the diff. A reviewer reads
setMixedAudience(true) and ticks the box. Everything looks correct and nothing is.
Why "just call it first" is harder than it sounds
The obvious fix is to configure before you initialise:
public class MyApp extends Application {
@Override public void onCreate() {
super.onCreate();
configureForMinor(); // surely this is early enough
initialiseAdSdks();
}
}
Application.onCreate feels like the beginning of your app. It is not the beginning
of the process.
Android starts ContentProviders before Application.onCreate
runs, and several SDKs ship one specifically so they can start themselves without you writing any
code — FirebaseInitProvider, MobileAdsInitProvider and their equivalents.
That is a convenience feature. It also means an SDK can already be running by the time your first
line executes.
So whether your configuration lands in time depends on which SDKs auto-start, what they do on start, and in what order the providers ran — none of which is visible in your source. Two apps with identical-looking code can behave differently.
The remedies are known: disable the auto-init provider in your manifest and initialise
explicitly, or pass configuration as manifest meta-data so it exists before any code
runs. Which applies depends on the SDK.
The point is not that this is unfixable. It is that you cannot tell whether you have fixed it by reading the code.
The dates are not the ones you think
Most coverage of this treats it as an American problem starting in 2026. Two things are wrong with that.
Brazil got there first. The Digital Statute for Children and Adolescents (Lei 15.211/2025, "ECA Digital") has been enforceable since 17 March 2026, with no grace period and no phased rollout — months before Texas. Google Play began returning Brazilian age signals on the same date.
It also reaches further than the US laws. It applies to any provider of services targeted at minors or merely likely to be accessed by them, explicitly including companies based outside Brazil. "We don't market there" is not an answer if Brazilian teenagers can install your app.
And Brazil bans the thing Play sometimes gives you. ECA Digital requires
reliable age verification and expressly prohibits mere self-declaration. Play's age
response carries an assurance tier: TIER_A means the user typed their own age.
TIER_D means government ID plus a selfie check.
So it is entirely possible to integrate Play Age Signals correctly, receive a TIER_A
response, act on it faithfully, satisfy Texas — and still not satisfy Brazil, because the underlying
assurance was self-declared. If your code treats all four tiers identically, that distinction is
invisible to you.
The rest of the map, as at August 2026:
| Status | Note | |
|---|---|---|
| Brazil — ECA Digital | in force 17 Mar 2026 |
reaches non-Brazilian companies; self-declaration prohibited |
| Texas — SB 2420 | enforceable under appeal |
see the note below the table |
| Utah | 6 May 2027 | safe harbour for relying on store data; AG enforcement removed in 2026, private right of action remains |
| Louisiana | 1 Jul 2027 | delayed by HB 977 (Act 185, May 2026); no safe harbour |
| EU — DSA Article 28 | in force Feb 2024 |
see below |
| UK — Online Safety Act | in force 25 Jul 2025 |
see below |
Texas, in detail, because it is moving. The Fifth Circuit stayed the district court's injunction on 28 May 2026, and the law took effect 4 June 2026. The Supreme Court denied emergency applications to vacate that stay on 6 July 2026 — ruling on the stay alone, not on the merits. The Fifth Circuit then heard the First Amendment merits on an expedited schedule on 4 August 2026 and has not yet ruled. The law is enforceable today; whether it survives is not settled.
Two regimes that are already in force, and work differently
The EU has been stricter than any of this for two years. DSA Article 28 does not ask platforms to act on an age signal. It prohibits targeting minors with personalised advertising outright. That is a ban, not a duty, and it predates every US statute above.
The catch is scope: it binds "online platforms" as the DSA defines them — services that store and disseminate user content to the public. A social or user-generated-content app is very likely one. A single-player game with a banner ad is not. Work out which you are before assuming either way.
The UK's answer is the opposite of the American one. Age assurance duties under the Online Safety Act have applied since 25 July 2025, enforced by Ofcom, to user-to-user services, search services and services publishing pornography. Ofcom's accepted methods are photo-ID matching, facial age estimation, Open Banking, digital identity services and mobile-operator checks.
An app-store age signal is not on that list. Where Texas and Utah give you a safe harbour for relying on store data, the UK regime may not accept it as discharging the duty at all. One integration does not answer both questions.
What the API actually returns
If you have integrated Play Age Signals, it is worth knowing the shape of it, because the shape is where the mistake lives.
It is two calls, not one. requestAgeSignalsAccess() returns an
AgeSignalsAccessResult carrying ageSignalsStatus — this is where
NOT_SHARED (the user declined) and VERIFICATION_REQUIRED (verification
pending) arrive. checkAgeSignals() returns an AgeSignalsResult carrying
the ages.
That splits the failure in two, and the first half is the one nobody writes about. An app that
only ever calls checkAgeSignals() cannot learn that the user declined to share
at all — the status is not on that object. It reads null ages and carries on. The second
half is the familiar one: an app that does make both calls but reads the ages without branching on
the status treats "we don't know" as "no restriction", which is the unsafe
default.
The result itself carries:
- Four assurance tiers in
ageRangeSource:TIER_Aself-declared,TIER_Bparent-managed,TIER_Cassessed via credit card or ID,TIER_Dgovernment ID plus selfie. ageLowerandageUpper— and note the accessors have nogetprefix. It isageLower(), notgetAgeLower(), whatever the naming in prose documentation suggests.- A parental-approval lifecycle in
significantChangeStatus:APPROVED,PENDING,DECLINED.nullis also returned for accounts with no recorded change — sonulldoes not mean approved. significantChangeApprovalDate, which tells an approval given long ago from one given moments before whatever the user just did.installId, which is how Play tells you a parent has revoked approval after installation.
Failures arrive as AgeSignalsException.getErrorCode(). The code distinguishes "Play
cannot answer right now" from "this app may not ask" — different problems, and an app that treats
them alike is failing open on the second.
One thing that will bite you when you go looking. The library ships no consumer
ProGuard rules. In a real shipping build I checked, the class names survived R8 intact while every
accessor had been renamed to a single letter — ageLower() had become a().
That does not affect your app, which is compiled against the real names, but it does mean you cannot
inspect a release build for these calls without the mapping file, and neither can anyone auditing
you.
Google also recommends calling the Play Integrity API alongside, so a tampered build cannot simply fake an adult response.
For testing, Google ships FakeAgeSignalsManager in a testing artifact: you can
simulate any of these states in unit and integration tests without a real minor account. It replaces
the production manager, so it is for your own test suite rather than anything an outside auditor
could use on a shipped build.
On iOS, Apple's counterpart is the DeclaredAgeRange framework,
introduced in iOS 26 — AgeRangeService.requestAgeRange(...). Same obligations, different
API, and it is easy to ship the Android half and forget the other one.
How would you know?
Two questions, and only one has a cheap answer.
"Which tracking SDKs are actually in my build?" That is answerable from the binary. So is "is the age-signals library in here at all?" — and that absence, if you find it, is definitive.
I wrote a scanner for this. It is free, open source, has no dependencies, and runs on your own machine — your build is never uploaded anywhere:
npx agesignals app-release.apk
It lists every advertising, analytics and attribution SDK compiled in, the restricted-mode call each one needs, and what that call must precede. It exits non-zero when something is missing, so it drops into CI.
"Is any of it wired correctly?" That is not answerable from the binary, and the
scanner says so on every report. Whether checkAgeSignals() is called, in what order
things initialise, whether the child-directed flag is actually on the outbound ad request — those
need the app running.
A static tool can prove a library is absent. It cannot prove your integration works. Anything that tells you otherwise is selling you a certificate, not an answer.
What I would actually do
- Run the scanner on your current release build. It takes seconds and tells you what is in there.
- Check the ordering, per SDK, against its own documentation. This is where the silent failures live.
- Handle
NOT_SHAREDandVERIFICATION_REQUIREDexplicitly, and do not let a null age fall through to the adult path. - Look at the tier if Brazil is in scope for you.
- Work out which regimes apply before assuming one integration covers them all. The UK and EU answers are not the American one.
- Verify at runtime before telling anyone you are done — with a real session, not a code review.
None of this is difficult. It is just quiet, and quiet failures are the ones that reach production.
The scanner is at github.com/alnyc-hub/agesignals — Apache-2.0, no dependencies, nothing leaves your machine.
This is not legal advice. Statutory dates in this area have moved repeatedly; check them against primary sources before relying on any of them. Dates above were verified on 7 August 2026.
Sources
- Use Play Age Signals API · Understand responses · Testing — Android Developers
- Apple DeclaredAgeRange · WWDC25: Deliver age-appropriate experiences
- Enforcement of Brazil's ECA Digital introduces new obligations for companies — Mayer Brown
- Brazil Regulates the Children and Adolescents Online Safety Act — Baker McKenzie
- Age Assurance in 2026: UK and EU — Lewis Silkin
- Texas App Store Law Takes Effect After Fifth Circuit Stays Injunction — Morrison Foerster
- Louisiana Delays App Store Accountability Effective Date — Alston & Bird