Age Checks That Don't Become a Database of IDs
A tick box is not an age check. Neither is keeping a copy of everyone's passport. There is a middle path: prove the phone number, ask the age question once, and store only the answer.
Most age gates on the internet are a single checkbox. The visitor clicks "I am over 18", the page opens, and nothing anywhere has been verified. Everyone involved knows this. It persists because the alternative has usually meant collecting identity documents, and that is a liability nobody wants on their servers.
Those are not the only two options. The interesting question is not whether to check someone's age — increasingly that is being decided for you — but how much you have to learn about a person in order to check it.
The tick box proves nothing, and everyone knows it
A self-declared age gate has one property: it records that a claim was made. It does not establish that the claim is true, it does not resist a second click, and it does not survive a determined thirteen-year-old for more than a second or two.
It survives because it is cheap and because, for a long time, it was treated as sufficient. That assumption has been eroding. Regulators in several jurisdictions have moved toward expecting age assurance — an actual check with some evidence behind it — rather than an unverified declaration, particularly for gambling, adult content and age-restricted goods. The specifics differ by country and change often enough that you should confirm your own obligations with counsel rather than with an article; the direction of travel, though, is consistent.
The overcorrection: becoming a document archive
The obvious response is to ask for identity documents. It works, and it creates a new problem: you are now holding a collection of passports and driving licences, which is precisely the kind of data that turns an ordinary breach into a catastrophic one.
Storing an identity document to answer "is this person over 18?" is a bad trade. The question is a boolean. The data you kept to answer it is not.
The principle worth holding onto: the check and the record of the check are two different things. A provider may need to see a document to form an answer. Your systems only ever need the answer.
Why the phone number comes first
An age answer floating on its own is not much use. "Someone, somewhere, proved they were over 18" does not tell you that the person now holding the session is that someone. The answer has to be attached to something you can recognise on a later visit.
A phone number the visitor has demonstrably just used is a reasonable anchor. With reverse OTP, the visitor sends a message from their own WhatsApp account rather than receiving a code, so control of the number is proved by the act itself. The age question is then asked of a person who has already shown they hold that number, and the answer is bound to it.
That ordering matters. Verify age first and you have an unattached fact. Verify the number first and the fact has somewhere to live.
1. The visitor starts
Your site opens a verification session and shows a QR code or a WhatsApp deep link, exactly as for any other flow.
2. The number is proved
They send the message from their own account. The phone half reaches verified, and the age step is still outstanding.
3. The age question
They follow a one-time link to the age provider. The same link is returned to you, so you can move them along in the browser they are already in.
4. The answer returns
The provider answers, the visitor lands back on your callback URL, and a second webhook carries the result.
What actually gets stored
This is where the design either holds its promise or quietly breaks it. On Phone-Verif's flow=age, what comes back and what is kept is deliberately narrow:
Kept
- Whether the visitor cleared the threshold
- What the threshold was
- When it was answered
- Which provider answered
Not kept
- Date of birth
- The identity document
- Anything else the provider saw to reach its answer
The backend does not return a date of birth in the first place, so there is no discipline to maintain and no field to accidentally log. The provider credentials live in server-side secrets rather than in any table a browser reads.
Four answers, not two
A boolean is tempting but wrong. The age step reports one of four states, and the difference between two of them matters a great deal to the visitor:
pendingThe phone number is proved and the age step is still outstanding. Keep polling — the overall status already reads verified at this point, which is the one trap in this flow.
verifiedThe provider answered yes. The threshold it was asked about is reported alongside, so a later reader knows what was actually established.
deniedThe provider answered no. This is a decision, and re-running it will not change the answer.
failedThe provider could not be reached, so nothing was decided. A retry is entirely fair — this is not a denial and should not be treated as one.
Collapsing failed into denied is the mistake worth avoiding. It turns an outage on the provider's side into an accusation against a customer, and it is invisible until someone complains.
The returning visitor
An age check that runs every single visit is an age check people route around. A passing result is cached against the phone number for a year, so someone who has already cleared the same threshold is not sent back to the provider — they re-prove the number over WhatsApp and the earlier answer carries forward.
When you need a fresh answer anyway — circumstances changed, or you are testing the provider round trip — ending the WhatsApp message with #reset skips the cache and sends the visitor back through the full check.
What this does not do
It is worth being blunt about the limits. This answers one question: is the person holding this phone number above a threshold you set. It is not identity verification, it is not KYC, and it does not tell you a name.
It also inherits the assumption underneath every phone-based check — that the number belongs to the person using it. That assumption is much stronger when control is proved by sending a message from an account than when it rests on receiving an SMS, which is the gap SIM swap attacks exploit. It is not unbreakable. A determined teenager with access to an adult's phone is a scenario no remote check fully closes, and any vendor claiming otherwise is selling something.
What it does close is the gap between a checkbox that establishes nothing and a document archive you did not want. For most age-restricted commerce, that is the gap that actually matters.
Adding it
It is the same two endpoints as every other flow, with one parameter changed. flow=age runs the usual WhatsApp verification and then the age step; an optional min_age overrides the threshold configured for your account, which defaults to 18. If the flow is requested on an account with no age provider wired up, the API answers 503 rather than a generic error, so a misconfiguration is distinguishable from an outage.
The age verification use case covers where this tends to be needed, and the integration guide has the request, the two webhook payloads and the status response shapes.