EUDI Wallet SCA
A wallet-presented SCA Attestation is an OpenID4VP presentation carrying transactional data, and TS12 states that its verified jti claim serves as the Authentication Code PSD2 requires for electronic payments. This guide walks a payment service through the processing order: reading type metadata, signing and encrypting the request, surviving the locale check, verifying the Key Binding JWT and gating on the amr array. Written throughout as a second acceptance path alongside an existing implementation, never as a replacement for one.
Technically reviewed by Olena Zaichenko, D.Sc.
- The verified jti is the regulatory artefact TS12 states that the jti claim inside the Key Binding JWT, once verified, serves as the Authentication Code PSD2 requires for electronic payments. That is the exact point where a protocol identifier becomes a compliance record, and it means there is no separate code to synthesize, no mapping table between an identity event and a payment event and no second identifier to hold beside the first. Persist the verified value with the payment record and the artefact is persisted.
- Key binding stops being optional The SD-JWT VC key-binding mechanism is optional in the base format and mandatory for an SCA presentation. TS12 requires the presentation to include a Key Binding JWT that binds the user's action to the transaction and guarantees the freshness and uniqueness of the presentation. A verifier that treats it as an extra is not verifying an SCA presentation.
- Your request builder is data-driven, not hard-coded Relying parties may only use transaction types and schemas declared in the transaction_data_types parameter of the attestation type metadata. Hard-coding a type that a given issuer does not declare produces a request that is invalid only against that issuer, so the defect stays hidden until the integration that exposes it.
- Two factors are evidenced, not asserted The amr array has to carry at least two different objects representing authentication categories. Your verifier inspects it and decides for itself instead of trusting a boolean. Wire it as a hard gate before authorization.
- Encrypt the request, and sign it Wallets are required to process encrypted presentation requests in line with JAR and OpenID4VP Section 5.10, and an unsigned request makes the wallet warn the user and demand an explicit confirmation before it continues. A request that fails the wallet's verification is dropped with no callback to you, so both halves are what separates a clean flow from a silent one.
A wallet-presented SCA Attestation is not a new authentication product. It is an OpenID4VP presentation carrying transactional data, and TS12 states that the jti claim inside its Key Binding JWT, once verified, serves as the Authentication Code PSD2 requires for electronic payments. That one sentence is what turns a protocol identifier into a compliance record. What follows is the processing order a payment service needs, and the boundary between what the wallet handles and what stays on your side of the integration.
The wallet is a second acceptance path, not a replacement
If you run strong customer authentication today, nothing in the EUDI Wallet specification set takes that build away from you. Technical Specification 12 (TS12) describes how a wallet-presented SCA Attestation is requested, presented and verified. It specifies a mechanism. It does not create a duty to use one, and this article is written accordingly: everything below is what happens once a payment service decides to accept this route alongside what it already operates. The distinction matters for planning and not only for accuracy. A team briefed that wallets are arriving, and that the existing flow is therefore on a deprecation path, will scope a migration, negotiate a cutover window and spend a quarter arguing about dual-running. A team briefed that this is an additional acceptance path scopes one new endpoint and one new verifier, then ships it behind a feature flag without touching anything that already works.
The obligation TS12 is built against is older than the wallet. The specification opens, at TS12 section 1.1, by stating that "Strong Customer Authentication (SCA) for payments is mandated under [PSD2] Article 97 (1) for electronic payment initiation, as well as for accessing a payment account online or other User's actions that present a risk of fraud." What that obligation has to achieve is a separate subject, covered on our PSD3 compliance requirements page. This page is about the wire format and the processing order.
Why a wallet can carry the factors at all
The justification TS12 gives for itself is a single sentence in TS12 section 1.1: "By design, the EUDI Wallet supports all three authentication factor categories required by [PSD2] (knowledge, possession, inherence) to enable SCA, as well as Dynamic Linking."
Dynamic Linking is the half that shapes your build, because it ties an authentication event to one specific payment rather than to a session. The terminology table in TS12 section 1.4 defines it as "Strong Customer Authentication that includes elements which dynamically link the transaction to a specific amount and a specific Payee." The adjacent cell of that same table row cites PSD2 Article 97 and PSD2-RTS Article 5 as the basis. Producing a payee string you are willing to put in front of a user is its own piece of work, and our Verification of Payee implementation guide covers that side of it.
Who issues, and who verifies
The attestation is a dedicated one, minted for this job. TS12 section 1.2 records that "The [ARF] Section 2.6.4 introduces the concept of dedicated SUA Attestations issued into the User's Wallet Unit by their ASPSP and then later presented in conjunction with transactional data to be signed by the Wallet Unit (for Dynamic Linking)."
That sentence splits the work into two builds that rarely land on the same team. Issuance belongs to the account servicing payment service provider, which mints a dedicated attestation into a wallet it does not control. Presentation belongs to whoever acts as the relying party at payment time. If you are on the presentation side, the substrate is the same verifier described in our EUDI Wallet verifier implementation guide, with transactional data layered on top. There is no separate payment protocol to learn, and what that is worth to a scope is worked through on that page.
The build, in order
Five things happen between a payment intent and a response your verifier can act on. Four of them carry a trap that a happy-path integration test will not surface. What you read out of that response once it arrives is the subject of the two sections after this one.
- Read the SCA Attestation's type metadata and derive from it what your request builder is permitted to ask for.
- Sign the authorization request, then encrypt it.
- Send transactional data the wallet can render in the locale the user has selected.
- Receive an OpenID4VP response profiled by HAIP.
- Verify the Key Binding JWT.
Step 1 - type metadata decides what you are allowed to ask
Detection runs off the attestation's own type metadata. TS12 section 3 requires that "For the Wallet Unit to determine whether a given attestation is an SCA Attestation, it SHALL evaluate the SD-JWT VC Type Metadata as per [SD-JWT-VC] section 6." Reading the sentence that follows that requirement in the pinned commit, the discriminator this article identifies is a top-level category claim carrying the value urn:eu:europa:ec:eudi:sua:sca. That is our reading of the source and not a quotation from it, because the claim name and the value both sit inside inline code markup. Confirm the value against the metadata your issuer publishes before you match on it.
The requirement that actually constrains your code sits one section later, and it binds the requester rather than the wallet. This is TS12 section 3.2: "To ensure compatibility, Relying Parties SHALL only use transaction types and schemas that are specified in the transaction_data_types parameter of the SCA Attestation's type metadata." Your request builder is therefore data-driven by construction. Hard-code a transaction type that one issuer's metadata does not declare and the failure stays invisible until you integrate with that issuer.
Step 2 - an unsigned request costs you a confirmation screen
A payment service that skips request signing does not get an error back. TS12 section 3.1 states that "If the request is not signed, the Wallet Unit SHALL warn the User and require explicit confirmation before continuing to process the request." The mechanism that follows is a warning and an explicit confirmation placed between the user and the payment, and it fires on every request that arrives unsigned. Provisioning one signing key removes the step.
Encryption is not optional either. From TS12 section 3.5: "Therefore, the Wallet Units SHALL support processing of encrypted presentation requests in compliance with [JAR] and [OID4VP] Section 5.10." Where the embedded disclosure policy check or the signature and certificate check does fail, the wallet stops and does not negotiate, and TS12 section 3.1 is explicit that "If this verification fails, the Wallet Unit SHALL cease processing the request and inform the User." Your side sees an abandoned payment and no callback, so instrument the gap between request dispatch and response arrival.
Step 3 - locale coverage is a hard failure, not a fallback
The specification reaches all the way into the consent screen. TS12 section 3.3.1 requires that "Wallet Solutions SHALL ensure that Users are able to give explicit and informed consent to a transaction."
The trap is what happens when a label is missing, and TS12 section 3.3.1 is blunt about it: "In case the transactional data's localised parameter names or the required UI elements' localised labels are not available to the Wallet Unit at the time of transaction, the Wallet Unit SHALL cease processing the transaction and inform the User." There is no fall back to a default language. Ship an attestation type whose metadata lacks the locale a user has picked and the payment dies instead of degrading. Your locale coverage matrix is a payment availability matrix, and it belongs in the same review as currency and scheme coverage.
Step 4 - the response is an OpenID4VP response profiled by HAIP
What comes back is the protocol response the verifier already knows. Per TS12 section 3.6, "After the User gives consent, the Wallet Unit SHALL provide a response according to [OID4VP] and [HAIP]." The same section of TS12 names the replay-relevant parts by number: "This includes handling the nonce and aud claims as described in sections B.1.3.1.5 and B.3.6 of [OID4VP] v1.0."
Step 5 - key binding is what makes the response evidence
Key binding, optional in the base credential format, becomes mandatory here. From TS12 section 3.6: "The presentation of an SCA Attestation in the [SD-JWT-VC] format SHALL include a Key Binding JWT (KB-JWT) to ensure cryptographic binding between the User's action and the transaction, and to guarantee the freshness and uniqueness of the presentation." A verifier that treats the KB-JWT as an optional extra is not verifying an SCA presentation at all.
The pivot - where a protocol artefact becomes a regulatory one

Everything to this point is ordinary credential plumbing. One sentence changes what the plumbing produces. TS12 section 3.6 sets out the claims a Key Binding JWT carries, and the entry for the jti claim closes with this: "Once verified, it serves as the Authentication Code required by [PSD2] for electronic payments." The pronoun stands for the value of that claim, which the entry names above the sentence and does not repeat inside it. The same section constrains the value, per TS12 section 3.6: "This value SHALL be unique for each presentation." Uniqueness per presentation is the whole of the stated constraint. Nothing in the quoted text specifies how the value is produced, so treat generation as your implementation choice and uniqueness as the requirement.
What that settles, and what it leaves open
It settles the artefact question, which is the one that usually produces an argument between the payments team and the identity team. A JWT identifier, unique to each presentation and checked by your verifier, is the thing TS12 says fills the Authentication Code role. There is no separate code to synthesize, no mapping table between an identity event and a payment event and no second identifier to store beside the first. Persist the verified jti with the payment record and the artefact is persisted.
It settles nothing about whether you have to accept the route. TS12 describes how the mechanism behaves; it does not say who must operate it. Whether wallet presentation becomes one of the authentication paths your institution offers stays a business and compliance decision, and the implementation you run today is untouched by that decision either way.
Persisting the identifier, and rejecting a repeat
TS12 requires the value to be unique for each presentation. The quoted requirement stops there and does not say what a verifier should do when a value arrives twice. What follows is this article's engineering recommendation and not a TS12 requirement.
A uniqueness requirement is only enforceable by the party that can see a repeat, and on this route that party is you. Store the jti of every presentation you accept, alongside the payment record you are already writing, and check an arriving value against that store before you treat the presentation as authenticated. A value you have accepted before is a replay signal, so reject the presentation and do not authorize the payment on it. How long you keep accepted values, and in what store, follows from your own record-keeping and reconciliation rules. TS12 gives no retention period and no storage model, and this page does not invent one.
Evidencing two factors without trusting the wallet
Your verifier can check the multi-factor property in the response itself. TS12 section 3.6 requires that "The amr array SHALL contain at least two different objects representing authentication categories." Your verifier reads the array and decides for itself. That is a different posture from a hosted authentication service returning a boolean you have to believe, and it is worth wiring as a hard gate and not a log line, because the array is where TS12 states the requirement. In practice that leaves two decisions in your code. The first is whether two entries drawn from the same category count, and the wording settles it by asking for different objects representing categories, not merely for two entries. The second is what your service does with a well-formed response carrying only one category. Reject it. A second attempt against the same wallet and the same attestation has no reason to return a different array.
Failure modes to wire before go-live
| Condition | Specified outcome | What you build |
|---|---|---|
| Authorization request not signed | Wallet warns the user and requires explicit confirmation before continuing | Sign every request so the extra confirmation step never fires |
| Signature, certificate or disclosure policy check fails | Wallet stops processing the request and informs the user | Timeout and abandonment telemetry, since no response reaches you |
| Requested transaction type absent from the attestation type metadata | Outside what a relying party is permitted to send | Metadata-driven request builder, validated per issuer |
| Locale-specific parameter name or interface label unavailable | Wallet stops processing the transaction and informs the user | Locale coverage tracked as payment availability |
| Key Binding JWT missing from the presentation | Not a conformant SCA presentation | Hard reject in the verifier, never a warning |
| Fewer than two authentication categories in the response | Two factors are not evidenced | Hard gate before the payment is authorized |
What stays on your side
TS12 draws a narrow boundary and it pays to name what falls outside it. Deciding when authentication is required at all is not in scope. Neither is your risk engine, your fraud scoring or the authentication path you keep for users who present no wallet. What TS12 does change is the evidence you collect at the moment of authentication.
Everything that follows from that lands in your codebase: a request builder driven by each issuer's type metadata, a locale coverage matrix reviewed beside currency and scheme coverage, telemetry on requests that never come back, a store of accepted jti values with the check against it and a hard gate on the amr array before the payment is authorized. None of that is payment protocol work, and all of it is yours to build and operate.
Pinning the document you build against
Two practical notes that will otherwise cost a day each.
First, the rendered documentation site does not carry TS12. The eudi.dev technical-specifications page for TS12 returns HTTP 200 with 61,555 bytes and zero specification text, because it renders a one-sentence stub wrapped in navigation chrome. Measured 2026-09-04. That is the worst kind of source, since fetching it succeeds. The full document is served from the GitHub blob URL cited throughout this article, and that URL is the one to point both your reading and your citations at.
Second, pin a version. The specifications repository carries no git tags, so branch main advances with no marker attached. The copy behind every quotation above is TS12 version 1.0.1 dated 2026-01-30, at commit ee91a294c833af5188726fd8c302c641212192aa. Reuse is unencumbered, per TS12, Licensing and Reuse: "This document is made available under the Creative Commons Attribution 4.0 International licence (CC BY 4.0), unless otherwise stated."
FAQ
Quick answers to common questions about custom software development, pricing, process and technology.
Type to filter questions and answers. Use Topic to narrow the list.
Showing all 5
No matches
Try a different keyword, change the topic or clear filters
-
No. TS12 specifies how the mechanism works and does not say who has to operate it. In build terms the wallet is a second entry into an authentication decision your system already makes, so what it needs is a branch and not a rewrite.
It also needs no new fallback: a presentation that halts inside the wallet returns nothing to you, so the path a user takes when the wallet route does not complete is the path you already run today.
-
TS12 describes dedicated attestations issued into the user's wallet by their account servicing payment service provider, then later presented together with transactional data that the wallet signs. Issuance and presentation are two separate builds and they rarely sit with the same team.
If you are on the presentation side you act as a relying party and never touch issuance, which carries a testing consequence worth planning for early: you cannot mint an SCA Attestation to test against, so your test matrix is a list of issuers whose wallets you can obtain a real one from.
-
The wallet does not reject it. TS12 requires the wallet to warn the user and demand explicit confirmation before it continues, so the request still proceeds but through a step your flow does not otherwise contain, on every unsigned request.
Signing and encryption are separate requirements and both apply, so scope them as one piece of key management work and not as two independent options.
-
By evaluating the SD-JWT VC type metadata. The discriminator this article reads out of the pinned commit is a top-level category claim carrying the SCA attestation URN, and payload shape is not what the match is made on.
TS12 puts this duty on the wallet, so it is a check your integration depends on and not one your own verifier performs.
-
Yes, and it stops the payment instead of degrading it. TS12 requires the wallet to cease processing the transaction and inform the user when the locale-specific parameter names for the transactional data, or the locale-specific labels for the required interface elements, are unavailable at the time of the transaction.
No fall back to a default language is defined. The part that catches teams out is where those strings live: they travel with the attestation type metadata, so your own product translations do not extend coverage and adding a language becomes a conversation with the issuer.
I work with startup founders who need a dedicated software development team but don’t want to gamble on hiring, random outsourcing, or opaque delivery.
Most founders face the same problem sooner or later.
Early technical and team decisions lock the product into tech debt, slow delivery, missed milestones and constant re-hiring. By the time this becomes visible, fixing it is already expensive.As a CTO and software architect, I help founders design, build and run dedicated development teams that work as a true extension of the startup. Not as a black-box vendor.
My focus is on complex products where mistakes are costly:
- Web3 and blockchain platforms
- FinTech and regulated products
- High-load startup systems
- MVP → scale transitions
We don’t do body-shopping.
We don’t sell generic outsourcing.Instead, we help founders:
- build the right team structure from day one
- keep technical ownership and transparency
- scale delivery without losing control
- avoid vendor lock-in and hidden risks
Teams are aligned with the product roadmap, business goals and long-term architecture. Not just short-term velocity.