If your app asks users for a photo of their passport or ID card today, you have probably heard that EU Digital Identity Wallets arrive "at the end of 2026". The date is real, and it is exact: 24 December 2026. But it is a deadline for the Member States, not for you, and it does not switch off document scans.
This post does three things. It shows where the date comes from, with the arithmetic. It lists what a wallet actually hands over, from the attribute tables in the rules. And it sketches a design that accepts a wallet when the user has one and falls back to a document scan when they do not. It is written by doc.cheap, a Passport and ID OCR API, so read the product parts with that in mind. doc.cheap reads document images; it does not read wallets.
Where 24 December 2026 comes from
The obligation is Art. 5a(1) of Reg. (EU) No 910/2014, inserted by Reg. (EU) 2024/1183, the eIDAS amendment. It says each Member State "shall provide at least one European Digital Identity Wallet within 24 months of the date of entry into force of the implementing acts referred to in paragraph 23 of this Article and in Article 5c(6)".
So the clock does not start with the eIDAS amendment itself. It starts with the Commission's implementing acts. The Art. 5a(23) acts are four Commission implementing acts dated 28 November 2024, among them Implementing Reg. (EU) 2024/2979 on the integrity and core functionalities of wallets. It was published in the Official Journal (OJ L, 2024/2979) on 4 December 2024. Its Art. 15 says it "shall enter into force on the twentieth day following that of its publication in the Official Journal of the European Union".
The arithmetic:
| Step | Date |
|---|---|
| Published in the Official Journal | 4 December 2024 |
| Day 1 after publication | 5 December 2024 |
| Day 20 after publication: entry into force | 24 December 2024 |
| Plus 24 months (Art. 5a(1)): wallets due | 24 December 2026 |
| Plus 36 months (Art. 5f(2)): some private relying parties must accept wallets | 24 December 2027 |
The "twentieth day following" counts from the day after publication, so 4 December plus 20 days lands on the 24th, not the 23rd. The same act count drives the second date in the table, which is the one most product teams actually care about (more on it below).
What a wallet hands over
A wallet does not send a picture of a passport. It presents signed data. The data set for a natural person is fixed in the Annex to Implementing Reg. (EU) 2024/2977, "person identification data" (PID). The Annex says PID is issued in two formats: ISO/IEC 18013-5:2021 and the W3C "Verifiable Credentials Data Model 1.1".
Here are the attributes, with the presence the text gives each one (Tables 1, 2 and 5 of the Annex, as published on 4 December 2024). The last column is the nearest field in a doc.cheap scan response, so you can see where the two worlds line up and where they do not.
| PID attribute | Presence | Nearest field in a document scan |
|---|---|---|
family_name |
mandatory | holder.surname |
given_name |
mandatory | holder.given_names |
birth_date |
mandatory | holder.birth_date (ISO 8601) |
birth_place |
mandatory | a birth_place entry in fields[], when the document prints one |
nationality |
mandatory (alpha-2, one or more) | holder.nationality (alpha-3, one) |
resident_address, resident_country, resident_state, resident_city, resident_postal_code, resident_street, resident_house_number |
optional | none on a passport data page |
personal_administrative_number |
optional | not the same thing; fields[] may carry a personal_number the document prints |
portrait |
optional | images.main_photo |
family_name_birth, given_name_birth |
optional | no curated field |
sex |
optional (codes 0, 1, 2, 3, 4, 5, 6, 9) | holder.sex (M, F, X) |
email_address, mobile_phone_number |
optional | not on a document |
expiry_date (metadata) |
mandatory | document.expiry_date, but of the document, not of the PID |
issuing_authority (metadata) |
mandatory | an authority entry in fields[], when printed |
issuing_country (metadata) |
mandatory (alpha-2) | document.issuing_state (alpha-3) |
document_number (metadata) |
optional | not the same thing: PID's number is assigned by the PID provider, document.number is the passport's |
issuing_jurisdiction, location_status (metadata) |
optional | none |
Three things in that table matter when you write the mapping code.
- Country codes differ. PID uses ISO 3166-1 alpha-2 (
DE). Passports and the machine-readable zone use alpha-3 (DEU). Keep one internal form and convert at the edge. - "Mandatory" does not mean "always known". Under Table 1 the text adds: "Where an attribute value is not known for the person or cannot otherwise be issued as part of the person identification dataset, Member States shall use an attribute value appropriate to the situation instead." Expect placeholder values, not missing keys.
- Only five attributes are mandatory about the person. Address, portrait, sex and birth names are all optional. If your flow needs one of them, the wallet may simply not carry it for a given user.
Who still arrives with a document
The deadline obliges each Member State to provide a wallet. It does not oblige anyone to use one. Art. 5a(15) is blunt: "The use of European Digital Identity Wallets shall be voluntary." It goes on: "It shall remain possible to access public and private services by other existing identification and authentication means."
So after 24 December 2026 you will still see:
- Travellers and customers from outside the EU. The recitals tie the wallet to "the legal identity of Union citizens, residents in the Union or legal persons". A visitor with a passport from elsewhere has no EU wallet to present.
- People who have not installed one, or cannot, or would rather not. The text protects that choice.
- Flows that need the document itself. Some processes want the document image, the document number or the machine-readable zone of the physical document, not an attestation about the person. A wallet's PID carries its own
document_number, assigned by the PID provider, which is not the passport's number. - The period before a given country's wallet is live. 24 December 2026 is the legal deadline. When each national wallet actually reaches users is a separate question, and the only reliable answer for any one country is that country's own announcement or the Commission's.
A design that takes both
The shape that survives all of this is simple: ask for a wallet presentation when the user has one, and fall back to a document scan when they do not. Both paths end in the same internal record.
user starts verification
|
v
offers a wallet? --yes--> wallet presentation --> verify signature --> map PID
| |
no v
| internal identity
v record
document photo --> POST /v1/scans --> map scan fields ----------------^
A few rules make the two paths interchangeable:
- Map both into one record with your own field names. Use the table above as the mapping.
- Keep the source. Store whether a record came from a wallet or from a scan. They carry different evidence, and a reviewer will want to know which.
- Treat nulls as honest. In a doc.cheap response every key is always present and an unknown value is
null. A wallet may send a placeholder instead. Normalise both to one convention. - Store as little as the flow needs. A scan can be run so that nothing is written down on the API side (below).
Here is the fallback call with plain fetch. The public sandbox key sk_sandbox_public is printed in the docs and needs no signup; it is rate limited per client address.
import { readFileSync } from "node:fs";
import { randomUUID } from "node:crypto";
async function scanDocument(path, apiKey = "sk_sandbox_public") {
const response = await fetch("https://api.doc.cheap/v1/scans", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": randomUUID(), // a retry returns the first result
},
body: JSON.stringify({
image: readFileSync(path).toString("base64"),
options: { retain_hours: 0, return_portrait: false }, // nothing stored
}),
});
if (!response.ok) throw new Error(`scan failed: HTTP ${response.status}`);
return response.json();
}
// Map a scan into the same record a wallet presentation would fill.
function fromScan(scan) {
if (scan.meta.status !== "recognized") return null;
const field = (name) => scan.fields.find((f) => f.id === `${name}@0`)?.value ?? null;
return {
source: "document_scan",
family_name: scan.holder?.surname ?? null,
given_name: scan.holder?.given_names ?? null,
birth_date: scan.holder?.birth_date ?? null,
birth_place: field("birth_place"),
nationality_alpha3: scan.holder?.nationality ?? null,
issuing_country_alpha3: scan.document?.issuing_state ?? null,
document_number: scan.document?.number ?? null,
mrz_status: scan.mrz.status, // "passed", "failed" or "absent"
};
}
meta.status is one of five strings: recognized, no_document_found, unreadable, unsupported_document or rejected. Only the first should fill a record; the others should send the user back to retake the photo. On a paid key the balance is charged only when the scan is billable.
We ran one call of this kind against sk_sandbox_public on 24 September 2026 at 21:17 UTC, with the product's own test document, a passport. The response came back with HTTP 200. Every document value below is masked, and fields, images and mrz.lines are trimmed:
{
"meta": {
"schema_version": "1.0",
"id": "<SCAN_ID>",
"status": "recognized",
"billed": true,
"confidence": "medium",
"timing": { "upload_ms": 255, "processing_ms": 409, "total_ms": 678 },
"created_at": "<RUN_TIMESTAMP>",
"reference": null
},
"document": {
"kind": "passport", "country": "<ISO3>", "country_name": "<COUNTRY>",
"issuing_state": "<ISO3>", "number": "<DOCUMENT_NUMBER>", "series": null,
"issue_date": "<DATE>", "expiry_date": "<DATE>", "is_expired": false, "days_remaining": "<N>"
},
"holder": {
"given_names": "<GIVEN_NAMES>", "surname": "<SURNAME>", "full_name": "<FULL_NAME>",
"birth_date": "<DATE>", "sex": "<SEX>", "nationality": "<ISO3>"
},
"mrz": { "status": "passed", "reason": null, "lines": ["<LINE_1>", "<LINE_2>"], "text": "<MRZ>" },
"quality": { "overall": "pass" },
"authenticity": { "overall": "not_checked", "checks": [] }
}
billed: true there means the scan was billable and counted against the sandbox's free allowance; the sandbox key itself is never charged. The fields array of that run included birth_place, authority and personal_number entries, which is where the table above points for those PID attributes. A passport data page carries its MRZ as two lines in the TD3 layout; the TD1, TD2 and TD3 formats page shows the layouts side by side.
Note authenticity.overall: "not_checked". A scan like this is recognition: it reads what is printed and checks the MRZ check digits. It is not forgery detection, and it is not the same assurance as a signed wallet presentation. If your process needs a stronger assurance level on the scan path, that has to come from somewhere else in your flow. If you are choosing a provider for the fallback, our passport OCR API comparison lays out the options, including ones that do more than recognition.
What to watch next
- 24 December 2026 – wallets due. Art. 5a(1), Reg. (EU) 2024/1183, counted from the entry into force of the implementing acts as shown above.
- 24 December 2027 – private relying parties. Art. 5f(2) says private relying parties that must use strong user authentication for online identification, by law or by contract, "shall, no later than 36 months from the date of entry into force of the implementing acts referred to in Article 5a(23) and Article 5c(6) and only upon the voluntary request of the user, also accept European Digital Identity Wallets". The text names transport, energy, banking, financial services, social security, health, drinking water, postal services, digital infrastructure, education and telecommunications, and it excludes micro and small businesses.
- Very large online platforms. Art. 5f(3) makes them accept wallets for user authentication, again only on the user's voluntary request and for the minimum data needed.
- Changes to the implementing acts. Recital 4 of Implementing Reg. (EU) 2024/2979 says the Commission "should review and update" it where necessary. Check the attribute tables in 2024/2977 before you freeze a mapping.
The practical takeaway: build the wallet path when your users' countries ship their wallets, keep the document path, and map both into one record from day one. The Passport and ID OCR API docs cover the scan side in full.
This is an engineering summary, not legal advice.