There is a single design decision that separates an age-verification verifier that works for everyone from one that works for most people: how it falls back. The EU age-verification verifier guidance does not leave this to taste. It states plainly that a verifier must support more than one presentation mechanism and must select the best available option at runtime. Implement only the happy path and you will quietly lock out every user whose browser or device does not support it.
This post lays out the fallback matrix, why it exists, and how to reason about it without over-claiming the privacy properties at the top of the stack.
Two mechanisms, two proof types
The matrix has two axes.
Presentation mechanism — how the request reaches the wallet and the response comes back:
- DC API (Digital Credentials API) — a browser-integrated API. The browser mediates the credential request directly, giving the smoothest user experience. This is the primary method, and it is increasingly available in modern operating systems and browsers.
- OpenID4VP (OpenID for Verifiable Presentations) — the OpenID-based protocol for requesting and receiving verifiable presentations. This is the fallback, used when the user's browser does not support the DC API. If you want the protocol fundamentals first, start with Understanding the OpenID4VP Protocol.
Proof type — what cryptographic form the proof takes:
- Standard mdoc attestation — the signed
mso_mdocProof-of-Age, made one-time-usable and issued in batches to limit linkability. - ZKP (Zero-Knowledge Proof) — an enhanced proof type offering stronger privacy properties, treated as the preferred option where available.
The guidance frames this as a runtime preference order — strongest privacy + best UX first, degrading gracefully:
| Priority | Mechanism + Proof | When it applies |
|---|---|---|
| 1 | DC API + ZKP | Browser supports DC API and a ZKP proof is available |
| 2 | DC API + mdoc | Browser supports DC API; standard mdoc proof |
| 3 | OpenID4VP + mdoc | Browser lacks DC API; fall back to OpenID4VP |
Your verifier must support the combinations, not just its favourite one, and pick the highest-priority option each device can actually satisfy.
Why the fallback is non-optional
It is tempting to ship DC-API-only and tell the long tail to upgrade their browser. Three reasons not to:
- Coverage. The DC API is increasingly available — which is precisely the phrasing that means not universally available yet. Every user on a browser or OS without it is a failed age check if you have no fallback. For an age gate, a failed check is not a degraded experience; it is a blocked transaction.
- The guidance requires it. "Your verifier must support all combinations" is not advisory. A conformant EU age-verification verifier implements the fallback.
- Graceful degradation is a reliability property. The same discipline we apply to any resilient integration — assume the best path may be unavailable and have a tested second path — applies here. The OpenID4VP fallback is your second path.
The privacy ordering, stated honestly
Putting ZKP at priority 1 is correct, but how you describe it to stakeholders is where teams get into trouble. Be precise about what is shipped versus preferred:
- Shipped and real: the standard mdoc proof is one-time-usable and batch-issued, and it discloses only over-threshold status — not date of birth, not identity. That alone defeats the naive correlation attack of "same credential presented twice."
- Preferred, not universal: ZKP is listed as a supported, preferred proof type. Do not document your system as "zero-knowledge, therefore non-correlatable by construction" unless the specific deployment you integrate with actually delivers ZKP for the transaction in question. The accurate sentence is: "Where ZKP is available we prefer it; otherwise unlinkability rests on one-time-use, batch-issued mdoc proofs."
We make the same careful distinction in our developer guide to the proof-of-age attestation and in Privacy-First Age Verification. It matters because a privacy claim is a compliance claim, and an over-stated one is a liability.
Implementing the runtime selection
The logic your verifier runs, conceptually, on each transaction:
- Feature-detect the DC API. Does this browser expose it? If yes, prefer it.
- Negotiate the proof type. Within DC API, request a ZKP proof; accept a standard mdoc if ZKP is unavailable.
- Fall back to OpenID4VP when the DC API is absent, with a standard mdoc proof.
- Validate before granting access. Regardless of path, the presented proof must be validated against the EU Age Verification Trusted List before you let the user through. That validation step is its own subject — see our companion post on validating proof-of-age against the trusted list.
- Treat the proof as single-use. Do not store it, do not replay it, do not derive a stable identifier from it. Doing so would re-introduce the very linkability the batch design exists to prevent.
Note that steps 1–3 are about reaching the wallet and steps 4–5 are about trusting and handling the response. A correct verifier does both. A common failure mode is teams that nail the request path and then mishandle the proof — storing it "for audit," which quietly converts a privacy-preserving check into a tracking record.
How this fits the bigger picture
The fallback matrix is the verifier-side mechanics of the same flow that, on the relying-party-identity side, depends on WRPAC registration: your verifier authenticates itself as a registered relying party, makes the request via DC API or OpenID4VP, receives a proof, validates it against the trust list, and grants or denies access. For a fuller comparison of how this verifier model differs from legacy identity checks, our EUDI Wallet vs. KYC developer comparison sets the two side by side.
The bottom line
The verifier guidance gives you a preference order — DC API + ZKP, then DC API + mdoc, then OpenID4VP + mdoc — and an instruction: support the combinations and choose the best one at runtime. The DC API gives you the experience; OpenID4VP gives you the coverage; the proof-type ordering gives you privacy. Ship only the top of the matrix and you ship an age gate that fails for the users least likely to be on a cutting-edge browser. Ship the whole matrix, describe the ZKP property honestly, and handle every proof as single-use, and you have a verifier that is correct, inclusive, and defensible.
This post reflects the EU Age Verification verifier guidance as of June 2026. The runtime fallback and proof-type ordering rest on the official verifier guide; verify against the current specification before implementing.
Share this article
Help others learn about eIDAS verification
