Payments · Security · Systems
⏱️
VOP · SEPA · 5 Seconds
Verification of Payee Engineering
Verification of Payee emblem: a cyan five second dial with a pink check mark

Five Seconds Before the Money Moves: Engineering Verification of Payee

Every euro area bank now asks another bank a question before your customer is allowed to send money: does this name belong to this IBAN? The answer has four values, not two, it must arrive inside five seconds, a broker in the middle does not move the obligation, and silence is a defined outcome the customer has to see. Version 1.1 of the scheme, its API specification and a shared security framework went live on 20 September 2026. This is the architecture of a blocking, third party check placed in the critical path of a payment, and the quiet ways it breaks.

ansi · wordmark · vop
██╗   ██╗ ██████╗ ██████╗
██║   ██║██╔═══██╗██╔══██╗
██║   ██║██║   ██║██████╔╝
╚██╗ ██╔╝██║   ██║██╔═══╝
 ╚████╔╝ ╚██████╔╝██║
  ╚═══╝   ╚═════╝ ╚═╝

Overview

In Plain Terms

When you send money to a new account in the euro area, your bank now asks the other bank one question first: does the name you typed belong to this account? You see the answer before you confirm. This article is about building that check well, especially what happens when the other bank is slow.

Word you will seeWhat it means here
IBANThe international account number you paste when paying someone.
PSPPayment service provider: your bank, or the app that sends the payment for you.
PayeeThe person or company receiving the money.
Verification of Payee (VoP)The name check that runs before you approve a euro transfer.
RVMA broker company that passes these checks between banks so each bank does not connect to every other bank.
EDSThe scheme's shared directory of which banks take part and how to reach them.
QWAC and mutual TLSA regulated digital ID card that each bank shows when they connect, so both sides know who is calling.

A customer opens your banking app, pastes an IBAN from an invoice, types "ABC Ltd" as the payee and taps Continue. Before the confirm screen appears, your backend has to resolve which bank holds that IBAN, open a mutually authenticated TLS connection to it or to a broker acting for it, send the name and the account number, and wait. The other bank compares the name against its own records and answers. Your app then shows one of four results, and only after that is the customer allowed to authorize the transfer. If the other bank says nothing for five seconds, your app has to say so, in words, with a warning.

That is Verification of Payee, required for euro credit transfers by Regulation (EU) 2024/886 and implemented across SEPA by the European Payments Council's VOP scheme. Euro area payment service providers had to offer it by 9 October 2025. Version 1.1 of the scheme rulebook, version 1.1.1 of the inter PSP API specification and version 2.1 of the API Security Framework all took effect on 20 September 2026. By April 2026 the EPC counted 2,768 PSPs and 58 routing and verification mechanisms adhering to the scheme.

VoP is usually described as a fraud control, and it is one. Architecturally it is something harder to get right: a synchronous dependency on a different institution, inserted before the payer has authorized anything, with a contractually defined failure mode. The name matching gets the attention. The shape of the contract around the call is where implementations actually fail.

⛔

It Blocks the Flow

The check runs after the payer enters details and before the payer is offered authorization. Nothing downstream starts until it resolves.

🎚️

Four Answers

Match, No Match, Close Match with the payee name, and Verification check not possible. A boolean model loses information you are required to show.

⏱️

Five Second Ceiling

The rulebook sets 5 seconds as the maximum, preferably 1 second or less, measured on the requesting side's own clock.

🧾

Silence Is an Outcome

No response in time means telling the payer the check could not be done and warning them. A late answer is discarded.

The Hard Part Is the Envelope

Name matching is a fuzzy string problem owned by the payee's bank, and each bank is free to tune it. What you own as the payer's bank is everything around the call: resolving the counterparty, holding a hard time budget, carrying four states through your product, and turning a timeout into a sentence the customer reads. Teams that start with the matcher end up with a good matcher behind a fragile envelope.

A Check That Blocks the Payment

In Plain Terms

Picture a cashier who reads the account holder's name back to you before you hand over the money. You can still pay if the name looks wrong, but you are told first, and the cashier has at most five seconds to get the answer. The money itself moves afterwards, on its own clock.

Article 5c of the amended SEPA Regulation fixes where the call sits. The payer's PSP "shall perform the service ensuring verification immediately after the payer provides relevant information," and it must do so "before the payer is offered the possibility of authorising that credit transfer." Two constraints follow at once. The check is on the interactive path, so a human is watching a spinner while it runs. And it is upstream of authorization, so it cannot borrow time from the payment itself.

The payment that follows has its own clock. For an instant credit transfer, Article 5a gives the payee's PSP 10 seconds from the time the payer's PSP receives the payment order. VoP sits in front of that window, not inside it. From the customer's point of view the worst case is a five second verification, then however long they take to read and decide, then up to ten seconds of execution. Your latency budget is the first of those three, and it is the only one with nobody else to blame.

01

Payer Enters Details

IBAN plus a name, or IBAN plus an organisation identifier such as an LEI.

02

Resolve the Counterparty

The Requesting PSP uses the EPC Directory Service to confirm the Responding PSP participates and to find routing data.

03

Send the Request

One POST per account, over mutual TLS, directly or through an RVM acting for the requester.

04

Match at the Payee's Bank

The Responding PSP compares against its own records and applies its own criteria.

05

Answer or Time Out

A result inside 5 seconds, or the requester's own timeout fires and the late answer is thrown away.

06

Show, Then Authorize

The payer sees the result and may still authorize. Then the credit transfer runs on its own clock.

PAYER APP REQUESTING PSP (RVM) RESPONDING PSP ───────── ────────────── ───── ────────────── IBAN + name ───────▶ validate input EDS lookup: is the payee's PSP a participant? where do I send this? start timer (own clock) POST /vop/v1/payee-verifications mTLS, PSD2 QWAC ────────▶ route ──────────▶ check QWAC NAN + BIC against EDS match name or id ◀──────────────── 200 + partyNameMatch ◀────────────────────────── (+ matchedName if CMTC) stop timer t <= 5 s ? use result t > 5 s ? NOAP to payer, discard late reply result + warning ◀─── authorize? ────────▶ SCT / SCT Inst (separate clock, up to 10 s for Inst)

Notice what the regulation does not do. Article 5c(5) says the verification "does not prevent payers from authorising the credit transfer concerned." VoP informs; it does not block the money. What it blocks is the screen. That distinction decides most of the design: the result is advisory, the timing is mandatory, and the payer's informed choice is the product.

Who Owes the Answer

In Plain Terms

Four parties are involved: you, your bank, the payee's bank, and sometimes a broker that carries the messages. The broker only carries the message. The responsibility for answering stays with the payee's bank, and the responsibility for telling you stays with yours.

The rulebook is a multilateral contract among participants and the EPC, governed by Belgian law, and it names the parties precisely. The names matter because the scheme separates who carries the obligation from who carries the packets.

Requester

The natural or legal person who intends to initiate a payment. In retail banking this is your customer. The Requester never talks to the scheme directly; everything it learns arrives through its own PSP.

Requesting PSP

The payer's PSP. It must send the VOP Request for the details the Requester supplied and then tell the Requester the result "instantly." It also decides how to measure the execution time and therefore where its timeout sits. This is the role most teams build first, and it owns the user experience end to end.

Responding PSP

The payee's PSP, which "receives the VOP Request from the Requesting PSP and Instantly processes that VOP Request." It holds the account data and runs the match. The Euro Banking Association's working group puts the consequence bluntly: the payee's PSP "bears full responsibility for the outcome and is therefore free to apply different criteria," so every PSP "may have a different approach" to name checks.

Routing and Verification Mechanism (RVM)

An intermediary that routes requests and responses and may run checks on behalf of a participant. RVMs must be registered in the EPC Directory Service to act for participants. The rulebook is explicit that using one "does not relieve the Responding PSP from its obligations stipulated under the Scheme," and its outsourcing clause says the same about participant liabilities in general.

EPC Directory Service (EDS)

The registry that "stores and maintains all required operational data about Participants." Requesting PSPs must use it to confirm the Responding PSP participates and may use it to find routing data. Participants must keep their own entries current and download the current copy. The Responding PSP also uses it for authorization, as the security section below shows.

Obligation and Topology Come Apart

An RVM in the path adds a hop to your latency budget and a party to your incident calls. It does not add a party to the liability. If the broker is slow, the Responding PSP still owes an answer inside the maximum execution time, and the Requesting PSP still owes the payer an explanation when none arrives. Outsource the plumbing if you like; the scheme will keep addressing the bank.

The adherence numbers explain why RVMs exist at all. With 2,768 PSPs in the scheme, a bilateral mesh where every bank connects to every other bank is not an operational option for most of them. Fifty eight RVMs is the market's answer to that fan out. Read it the same way you read a payment gateway: a convenience for connectivity, never a transfer of accountability.

The Wire Contract

In Plain Terms

The message itself is small: the name you typed, the account number, and which banks are involved. The answer is one of four short codes. Most of the hard work is in what your bank does with that answer.

The inter PSP API specification (EPC103-24 version 1.1.1) is small, which is a compliment. It defines one mandatory endpoint, POST /vop/v1/payee-verifications, two mandatory request headers, a request body that names the payee and both banks, and a response that carries match codes. There is no bulk endpoint: section 2.1 requires a separate VOP Request for each Payment Account Number concerned.

# Illustrative request, field names from EPC103-24 v1.1.1 sections 4.1.2.1 and 4.1.2.2 POST /vop/v1/payee-verifications HTTP/1.1 Content-Type: application/json X-Request-ID: 6f1c2a9e-5b7d-4c1e-9a3f-2d8e4b6c1a07 X-Request-Timestamp: 2026-10-10T08:15:02.31Z { "party": { "name": "ABC Ltd" }, "partyAccount": { "iban": "BE68539007547034" }, "partyAgent": { "financialInstitutionId": { "bicfi": "PAYEBEBBXXX" } }, "requestingAgent": { "financialInstitutionId": { "bicfi": "PAYRDEFFXXX" } } } # Illustrative response: a close match carries the name the payee's bank holds HTTP/1.1 200 OK X-Request-ID: 6f1c2a9e-5b7d-4c1e-9a3f-2d8e4b6c1a07 X-Response-Timestamp: 2026-10-10T08:15:02.870Z # invalid! see "The Quiet Breaking Changes" { "partyNameMatch": "CMTC", "matchedName": "ABC Limited" }

The party object has two alternatives. Either a name of up to 140 characters, mandatory for a natural person, or an identification.organisationId carrying an lei, an anyBIC, or an others block with a scheme name. The spec states that a natural person can only be verified by name plus IBAN. The response returns partyNameMatch or partyIdMatch, and matchedName "must be provided if and only if the result of partyNameMatch is "Close Match"."

Code Meaning Name + IBAN Identifier + IBAN
MTCH Match Yes Yes
CMTC Close Match, with matchedName Yes Not possible
NMTC No Match Yes Yes
NOAP Verification check not possible Yes Yes, if the identifier type is supported at EDS level

The last cell hides a rule worth reading twice. When an identification code type is supported at EDS level but not available for this payee, the answer is HTTP 200 with NOAP. When the type is not supported at EDS level at all, the spec says HTTP 400 should be used. One is a business answer you show the payer. The other is an error your integration caused. If you collapse them, you will page someone for customers typing VAT numbers into a field your counterparty never supported.

Errors are Problem Details

Failures use application/problem+json with mandatory type and code and optional title, status, detail and instance. Four codes are defined: FORMAT_ERROR (400) for malformed JSON, missing or mutually exclusive fields, unknown fields and invalid values; TIMESTAMP_INVALID (400); CLIENT_INVALID (401) for an unlisted client or a certificate that is not a valid PSD2 QWAC; and CLIENT_INCONSISTENT (401) when the certificate's identifier and the request BIC do not match. Unexpected server exceptions return 500. Those four HTTP statuses, 200, 400, 401 and 500, are the whole defined surface.

The spec cites RFC 7807 for this format. RFC 9457 obsoleted RFC 7807 in July 2023, adding a registry of common problem types and guidance for representing multiple problems. The wire shape is compatible, so nothing breaks, but if your shared error library already follows 9457, check that it does not emit fields your counterparties will reject as unknown.

Four Values, Not Two

In Plain Terms

There are four possible answers, not just yes or no: it matches, it nearly matches (and here is the real name), it does not match, or the check could not be done. Each one needs its own screen and its own record, because if the payment goes wrong later, that record shows what you were told.

The fastest way to build a bad VoP integration is to map the response to a boolean called verified. Each of the four values implies a different screen, a different warning, a different log record and a different fraud signal. The regulation already specifies most of the screens: on a close match the payer's PSP must show the name associated with the account, and on no match it must warn that "authorising the credit transfer might lead to transferring the funds to a payment account not held by the payee."

Result What the payer sees What you persist Signal for fraud and support
Match Confirmation, proceed Result, request ID, timestamps Low risk, still not proof of intent
Close Match The name the payee's bank holds, with the option to correct or continue Result, the displayed matchedName, the payer's choice Usually a typo or a legal vs trade name gap
No Match The regulatory warning, with the option to continue Result, warning shown, the payer's choice Highest risk, and the case most likely to be argued later
Not possible An explanation that the check could not be done, plus the same warning Result, cause (counterparty answer or your timeout), the payer's choice Operational: track by counterparty

The column that teams forget is the payer's choice. Article 5c(8) says a PSP "shall not be held liable for the execution of a credit transfer to an unintended payee" on the basis of a wrong identifier, "provided that it has fulfilled the requirements of this Article." The evidence that you fulfilled them is a record of which result came back, which text the payer saw and what the payer did next. The rulebook allows the Requesting PSP to process and store response data for providing the response to the Requester and for dispute resolution, so keep exactly that, and keep it joinable to the payment that followed.

Close Match is a disclosure, so treat it as one

A close match returns a real account holder's name to someone who typed a slightly different one. The rulebook limits what comes back so that names of other account holders on the account are not revealed, and leaves how the name is displayed to the Requesting PSP. That makes matchedName personal data crossing an institutional boundary on the strength of a near guess. Do not log it into general application logs, do not ship it to analytics, and do not let it end up in a client side cache that outlives the screen.

The UK ran this experiment first

Pay.UK's Confirmation of Payee has the same four shapes under different names: Match, Close match (where the payer is shown the account holder's actual name), No match, and Unavailable. UK Finance lists the causes of Unavailable as a timeout, an opt out, an account that does not exist or an institution that has not enabled the service. The useful lesson is that the fourth state has many causes and one screen. Your logs need the cause even when the payer only needs the screen.

Property SEPA Verification of Payee UK Confirmation of Payee
Basis Regulation (EU) 2024/886, EPC scheme rulebook Industry service overseen by Pay.UK
Results Match, No Match, Close Match with name, Verification check not possible Match, Close match with name, No match, Unavailable
Identifier path Name + IBAN, or organisation identifier + IBAN (no close match on identifiers) Name + account details
Time limit 5 s maximum, preferably 1 s or less Not stated on the UK Finance guidance page; timeout listed as a cause of Unavailable
Charge to the payer Free of charge by law (Article 5b(2)) Not stated on the guidance page
Warning Fatigue Is a System Property

The Euro Banking Association's working group notes that clients are "often unsure whether it is safe to authorise the payment" after a No match or a Verification not possible, and that frequent mismatches may teach them to disregard warnings. Every spurious NOAP you cause with a slow integration, and every NMTC caused by "ABC Ltd" versus "ABC Limited," spends down the credibility of the one warning that is supposed to stop an authorized push payment fraud. Your timeout rate is a fraud control metric.

The Five Second Contract

In Plain Terms

Five seconds sounds generous until you count what happens inside it: finding the other bank, a secure handshake, a possible broker, the other bank's search, and the trip back. Good systems prepare as much as possible before you even finish typing, so the check feels instant.

Section 3.3.2 of the rulebook is the most consequential paragraph in the scheme: "The maximum execution time for the Requesting PSP to get the VOP Response is 5 seconds (preferably 1 second or less)." Bilateral or multilateral agreements may set something shorter for the parties to them. The Requesting PSP chooses how to calculate the time, and may measure from its own system's send and receive times without relying on the timestamp attributes in the messages. The API spec repeats it: the cycle "is started by the Requesting PSP and ends when this Requesting PSPs receives the VOP response."

That puts the whole chain inside one clock that you own. Everything between your send() and your receive() is charged to the same five seconds: your own egress, the TLS handshake if the connection is cold, the RVM if you use one, the Responding PSP's matcher, and the way back. Here is one way to split it. The split is a design choice for your system, not a scheme requirement.

REQUESTING PSP CLOCK (the only clock that counts) 0 ms ─────────────────────────────────────────────────────────── 5000 ms │ │ ├─ EDS lookup ──┤ local cache of the downloaded EDS copy, ~0 network ms │ ├─ connect ─┤ warm pool: 0, cold mTLS handshake: costly │ │ ├─ RVM hop ─┤ optional, both directions │ │ │ ├──── responder match ────┤ │ │ │ │ ├─ back ─┤ │ │ │ target p99 for the whole span: 1000 ms (the rulebook's preference) │ │ hard abort: below 5000 ms, leaving room to render the NOAP screen │ └─────────────────────────────────────────────────────────────────────┘

Keep connections warm, because the handshake is not free

Mutual TLS with a qualified client certificate and an EV server certificate means a full handshake, chain validation and, on the responding side, an EDS check of your certificate identifier against your BIC. Paying that per request inside a five second budget is a choice you do not have to make. Hold a pool of long lived connections per counterparty endpoint or per RVM, and measure handshake time separately from request time so you can tell a slow peer from a cold pool.

Resolve routing before the payer finishes typing

The EDS lookup needs only the payee's bank identifier. Participants are required to download the current copy of the directory, so the lookup can be a local read. Run it as soon as the IBAN validates, while the payer is still typing the name, and the first step of your budget costs effectively nothing.

Abort before the ceiling, not at it

If your HTTP client times out at exactly 5,000 ms, you have no time left to write the record, choose the screen and send it to the device. Set the abort a few hundred milliseconds earlier, enforce it with a real cancellation rather than a timer that only stops waiting, and treat the remainder as the budget for the failure path.

// Requesting side: one budget, one cancellation, four outcomes. // Design sketch, not scheme text. vopTimestamp() is defined in "The Quiet Breaking Changes". async function verifyPayee(req, { budgetMs = 4600 } = {}) { const started = performance.now() const ctrl = new AbortController() const timer = setTimeout(() => ctrl.abort(), budgetMs) try { const res = await pool.post('/vop/v1/payee-verifications', { headers: { 'X-Request-ID': req.id, 'X-Request-Timestamp': vopTimestamp() }, body: req.body, signal: ctrl.signal, }) if (res.status === 200) return { outcome: res.body.partyNameMatch ?? res.body.partyIdMatch, matchedName: res.body.matchedName, cause: 'answered' } return { outcome: 'NOAP', cause: `http_${res.status}:${res.body?.code}` } // 400 is your bug } catch (e) { return { outcome: 'NOAP', cause: ctrl.signal.aborted ? 'own_timeout' : 'transport' } } finally { clearTimeout(timer) metrics.observe('vop_latency_ms', performance.now() - started, { peer: req.peer }) } }

Designing the Timeout

In Plain Terms

If the other bank does not answer in time, your bank cannot quietly try again later. It must tell you straight away that the check could not be done, and warn you. That is why the 'could not check' screen deserves as much care as the happy path.

In most distributed systems a timeout is an internal event. You retry, you fall back, you swallow it and serve something stale. VoP removes all three options. When no response arrives within the maximum execution time, the rulebook requires the Requesting PSP to instantly tell the Requester that the details could not be verified, reported as Verification check not possible, and to warn that authorizing the payment may lead to transferring funds to an account not held by the payment counterparty. A response that arrives late must be discarded. The failure path is a scheme specified, customer facing outcome.

No retry inside the window, and none after it

A retry that starts at second three and finishes at second six is a late response, and the rulebook tells you to throw late responses away. Inside one verification, retrying only makes sense on a fast transport failure such as a refused connection, early enough that a second attempt fits the remaining budget. After the window, do not retry in the background and quietly upgrade the screen. The payer was told the check was not possible; changing the answer under them makes your evidence trail incoherent.

The request ID is yours to make useful

The API defines X-Request-ID as a mandatory UUID, "The Requesting PSP's reference number of the VOP Request," and says nothing about uniqueness or idempotency. Make it unique per verification attempt, write it before you send, and join it to the payment that follows. When a payer disputes a transfer months later, the request ID is the only key that ties the screen they saw to the message the other bank answered.

Caching is a compliance question, not a performance trick

It is tempting to cache a Match for a payee the customer pays every month. The rulebook limits the Requesting PSP to processing and storing response data "for the purpose of providing a Response about such information to the Requester and/or dispute resolution," and Article 5c ties the check to the moment "immediately after the payer provides relevant information." Neither text invites serving yesterday's answer for today's payment. If your product wants saved payees to feel instant, get that from warm connections and early routing, and verify again.

Batches and the business opt out

There is no bulk call, so a payroll file of 400 transfers is 400 verifications. Article 5c(6) gives PSPs a pressure valve: PSPs "shall provide PSUs that are not consumers with the means to opt out from receiving the service ensuring verification" when they submit multiple payment orders as a package, and those users can opt back in at any time. Model the opt out as an explicit, auditable account setting with a timestamp, not as a flag a batch job sets on itself.

A Timeout Is a Sentence

Write the Verification check not possible screen first, before the happy path. It is the screen your payers will see whenever a counterparty, an RVM or your own pool is slow, and its wording carries the warning the regulation requires. If the copy for that screen is an afterthought, so is your compliance with the part of the rule that matters most under load.

One Security Baseline, Three Schemes

In Plain Terms

Banks prove who they are with special digital certificates, a bit like passports checked at both ends of the connection, and then look each other up in the shared directory. If a certificate expires or is set up wrong, the checks stop working.

Version 2.1 of the EPC API Security Framework (EPC164-22) became mandatory on 20 September 2026 for participants using APIs in three schemes at once: Verification of Payee, SEPA Request to Pay and SEPA Payment Account Access. The framework secures the API "as an 'envelope'," not the content of each message, and the envelope is mutual TLS.

Control What VOP requires Source
Transport Mutual authentication via TLS with client authentication; TLS version, key lengths and algorithms per the EPC payment security group's recommendation Framework section 6
Client certificate PSD2 QWAC with the ETSI TS 119 495 extensions; PSP role, competent authority name and a PSP identifier starting "PSD" Framework 4.3, API spec 2.4.1
Server certificate EV TLS certificate; the server does not need a QWAC and must send intermediates in the handshake Framework 4.3, Annex 3
Authorization Responding PSP extracts the identifier from the client QWAC and checks that it and the BIC in the payload appear together in the EDS API spec 2.4.2
Message signing None for VOP; sealing with QSealC is foreseen only for Request to Pay so far Framework section 9
Audit and availability Audit log always enabled and protected from manipulation; high availability including DDoS protection Framework sections 10 and 12

The authorization row is the interesting one. There is no OAuth token and no API key. Identity is the certificate, and permission is a directory lookup: the Responding PSP pairs the regulated identifier inside your QWAC with the BIC you claimed in requestingAgent and checks that the EDS lists them together. If they do not match you get CLIENT_INCONSISTENT. If the certificate is not a valid PSD2 QWAC, or you are not listed, you get CLIENT_INVALID. That means EDS freshness is a security dependency as well as a routing one, on both sides of the call.

The certificate clarification that bites in 2026

Annex 3 of the framework adds certificate detail for VOP. Any QWAC used for VOP client authentication "must include the Client Authentication EKU." The same annex notes that "by June 15, 2026, public TLS server certificates can no longer include the Client Authentication EKU," and that the QWAC must include the AIA extension by default so the server can build the chain, since the client is not expected to send the entire trust chain itself. The section that covers servers which also act as call back clients requires both clientAuth and serverAuth in the extended key usage.

Put those together and one shortcut is gone: a single public server certificate can no longer double as your outbound client identity. Your inbound VOP endpoint (EV TLS) and your outbound VOP client (QWAC with client authentication) are separate certificates with separate renewal paths. Inventory them that way, and alert on both expiries.

Shared Baseline, Shared Blast Radius

One framework across three schemes means one certificate estate and one TLS posture can serve VOP, Request to Pay and account access APIs. It also means one expired QWAC or one misconfigured trust store can take all three down together. Consolidating the baseline is efficient; consolidating the failure domain is a decision you should make on purpose.

The Quiet Breaking Changes

In Plain Terms

A small rule about how times are written means that, if software writes times the usual default way, roughly one request in ten is rejected. These tiny details break payments quietly, which is why the rules need automated tests.

The EPC's 16 March 2026 notice described the API changes in version 1.1.1 as clarifications: empty API fields "are no more allowed to be consistent with the standards," the character set is clarified, timestamps are clarified, and HTTP header handling, including content type, is refined. Each of those reads like errata. Each of them can fail in production on a fraction of traffic, which is the worst kind of failure to diagnose.

Empty is now invalid

The text types (Max35TextVop through Max500TextVop) have a minimum length of one character and must not start with whitespace. A missing mandatory field is a FORMAT_ERROR. Any serializer that emits "" for an unset optional string, or passes a name through with a leading space copied from a spreadsheet, now produces 400s on exactly the inputs your customers paste most often. Strip and omit; never send empty.

The character floor is narrow

HTTP bodies are UTF-8, but PSPs are only required to accept a-z, A-Z, 0-9, space and / - ? : ( ) . , ' +. Other Latin characters need prior agreement, with conversion guidance in a separate EPC document. Payee names with diacritics are common in SEPA. If you send "Müller" and the counterparty only guarantees the floor, the outcome depends on a bilateral detail you may not know. Decide on a transliteration policy for outbound names, apply it consistently, and log both the original and the sent form.

Timestamps: one in ten fails

Section 4.2.9.2 says the format "does not allow for trailing zeroes for milliseconds," and gives a regular expression whose fractional part must end in a digit from 1 to 9. The spec's own example marks 2025-07-10T14:36:25.46Z valid and the same instant written as .460Z invalid. JavaScript's Date.prototype.toISOString() always writes three fractional digits. Running the spec's regex against every millisecond value from 0 to 999 for this article, 100 of 1,000 default timestamps failed: every one whose millisecond value ends in zero.

That is a defect that passes every unit test with a fixed clock and then rejects about one request in ten with TIMESTAMP_INVALID in production, randomly, forever. It also means a check that your counterparty validates strictly and another does not. The fix is one line.

// The pattern from EPC103-24 v1.1.1 section 4.2.9.2, anchored for testing const VOP_TS = /^[0-9]{4}(-[0-9]{2}){2}T[0-9]{2}(:[0-9]{2}){2}(\.[0-9]{0,2}[1-9])?(Z|([-+][0-9]{2}(:[0-9]{2})?))$/ new Date(Date.UTC(2025, 6, 10, 14, 36, 25, 460)).toISOString() // '2025-07-10T14:36:25.460Z' fails // Drop trailing zeros, and the dot if nothing is left const vopTimestamp = (d = new Date()) => d.toISOString().replace(/\.?0+Z$/, 'Z') vopTimestamp(new Date(Date.UTC(2025, 6, 10, 14, 36, 25, 460))) // '2025-07-10T14:36:25.46Z' passes vopTimestamp(new Date(Date.UTC(2025, 6, 10, 14, 36, 20, 0))) // '2025-07-10T14:36:20Z' passes // Checked against all 60,000 millisecond values in a minute: 0 failures

Python's datetime.isoformat() fails differently: with microseconds it writes six fractional digits, which the pattern also rejects. Whatever your language, put the regex from the spec in a property test that sweeps the millisecond field, and run it against your response serializer too, because X-Response-Timestamp follows the same rule.

Errata Are Releases

A specification bump labelled "clarifications" still changes what a strict peer accepts. With thousands of participants, some counterparties validate to the letter on day one and some never do, so the same bug produces errors only on certain routes. Treat every scheme version as a release of your integration: diff the spec, add conformance tests, and watch error codes per counterparty for a week after the effective date.

Failure Modes and Operations

In Plain Terms

Most problems will not look like an outage. They look like one kind of answer slowly becoming more common on one route, or customers seeing warnings they did not expect. Watching those patterns is how you catch them early.

Most VoP incidents will not look like outages. They will look like a rising share of one result code on one route, or a support queue full of customers who saw a warning they did not expect. Instrument for that.

Failure What you observe Likely cause Response
Own timeout spikes NOAP with cause own_timeout on one counterparty or RVM Slow responder, slow broker, cold connection pool Split handshake from request latency; compare direct and RVM routes
Intermittent 400s TIMESTAMP_INVALID on roughly a tenth of calls Default ISO serializer writing trailing zeros Fix the serializer; add the regex sweep to CI
Steady 400s on one input class FORMAT_ERROR on pasted names Empty strings, leading spaces, characters outside the floor Normalize before send; log original and sent forms
All calls 401 after a change CLIENT_INVALID or CLIENT_INCONSISTENT Renewed QWAC without client authentication EKU, BIC and identifier not paired in EDS Certificate inventory with EKU checks; EDS entry review before rollover
Routing to non participants Calls to banks that are not in the scheme Stale local copy of the directory Refresh EDS on schedule; alert on copy age
No Match surge on one payee NMTC concentrated on a corporate IBAN Legal vs trade name, truncation, virtual accounts Route to support playbook; not an incident, but a customer experience problem
Your responder is the slow one Peers report your NOAP rate Matcher latency on large name sets, database contention Set your own p99 target well under 1 s and alert on it

Name matching is the payee bank's problem, and its causes are predictable

The Euro Banking Association's working group catalogues the usual causes of spurious results: companies registered under several names, so "different entities or branches use the same account"; legal form variants such as "ABC Ltd vs ABC Limited"; diacritics and transliteration, which the regulation's own recital names as examples of almost matching; name fields of 140 characters in one scheme and 70 in another, with "automatic truncation of longer legal names at payment initiation"; and virtual accounts whose underlying holders "are usually not verified or disclosed." If you run the responding side, those five causes are your test suite.

The metrics that matter

Per counterparty and per route: latency at p50, p95 and p99 against the 1 second preference and the 5 second ceiling; the distribution of MTCH, CMTC, NMTC and NOAP; the share of NOAP that is your own timeout versus a counterparty answer; error codes by type; and the share of payers who continue after each result. The last one is your best early signal that warnings have stopped working.

Cost Reality Check

In Plain Terms

Customers cannot be charged for this check, so banks carry the whole cost. The cost you can feel is time: every second of waiting and every unnecessary warning.

There is no price list to quote, and that is the point. Article 5b(2) of the amended regulation says the verification services "shall be provided to all PSUs free of charge." The payer never sees a fee, so every cost lands on the PSPs, and none of it can be passed through per check.

🧮

Per Payment, Not Per Batch

One request per account, no bulk call. Volume scales with payments initiated, not with files uploaded, unless business users opt out.

🔀

Broker or Mesh

58 RVMs exist because connecting directly to thousands of PSPs is expensive. Their commercial terms are private; their latency is your budget.

🔐

Certificates and Directory

Qualified client certificates, EV server certificates, EDS participation and upkeep. Fixed costs that recur on renewal schedules.

🛡️

Both Sides of the Call

Every PSP is a requester for its payers and a responder for everyone else's. You pay to ask and you pay to answer.

The Expensive Resource Is the Customer's Patience

The bill you can actually see is in human time: up to five seconds of spinner on every new payee, plus the reading time of every warning. The rulebook's preference for one second or less is the number to engineer against, and spurious warnings are the cost that compounds, because each one makes the next real warning cheaper to ignore.

Building Against a Moving Specification

In Plain Terms

The rules change again with version 2.0, planned to go live in November 2027. Build the system so that new answer codes fall back to a safe screen instead of breaking.

Version 1.1 is a minor update for errata and clarifications. Version 2.0 is not. The EPC ran a public consultation on 2.0 change requests from 1 April to 30 June 2026, and its May 2026 update to the ECB's payments advisory group listed 27 change requests, publication planned for November 2026 (27 November on the roadmap) and go live planned for November 2027. The notable ones change your data model and your routing:

Change request What it proposes What it would touch
CR 7, 8, 9 More granular NOAP codes, extended HTTP error codes Your four state model gains sub states; your error mapping grows
CR 13 Make VOP scheme agnostic, extending to non EPC and non euro schemes in the EEA Which payments trigger a check
CR 23 EDS search key based on IBAN instead of BIC Your routing lookup and directory cache
CR 26 Extend VOP to non EEA SEPA countries Counterparty coverage and test matrix

Deadlines move too, in both directions. On 10 September 2026 the EPC announced that the Payment Scheme Management Board had delayed the 15 November 2026 end date for the unstructured address format across all five EPC payment scheme rulebooks, with a new date to be set at an October 2026 meeting. That notice is about payment messages, not the VOP API, but it is the same lesson: a date printed in a rulebook is a plan until the board confirms it.

Design for the next version now

Keep the scheme code as a string in your domain model, with the four known values mapped to behavior and an explicit default for anything new, so a finer grained NOAP in 2.0 degrades to today's screen instead of a crash. Put the routing lookup behind an interface that takes the IBAN even though today it keys on the BIC. Version your conformance tests by spec version, because for a while you will answer peers on different versions.

Where the Sources Disagree

Are milliseconds required?

The API specification defines X-Request-Timestamp as a timestamp with "milliseconds included," yet the regular expression in section 4.2.9.2 makes the fractional part optional, and the document's own examples treat 2025-07-10T14:36:25Z as valid for a zero millisecond value. This article follows the regex and the examples, since a strict peer validating against the pattern accepts both forms. If a counterparty rejects timestamps without a fraction, that is the clause they are reading.

The endpoint path

Section 4.1.1 lists the mandatory endpoint as /vop/v1/payee-verifications. The examples in section 5 use an /api/vop/v1/ prefix, and section 5.2 uses the singular payee-verification. The document does not reconcile them. This article uses the path from 4.1.1. In practice the base URL comes from the routing data, so confirm the full path per counterparty rather than hard coding it.

"Proposed" regulation, adopted law

The EPC's landing page for rulebook version 1.1 still refers to "the proposed EU Instant Payments Regulation (IPR) amending the SEPA Regulation." The regulation was adopted on 13 March 2024 as Regulation (EU) 2024/886, and its verification obligation has applied in the euro area since 9 October 2025. The wording is stale; the obligation is not.

Problem details and a cross reference

The API specification cites RFC 7807 for error bodies; RFC 9457 obsoleted it in July 2023. Separately, section 8.3 of the Security Framework points to "chapter 7" for certificate authentication, while the certificate requirements are in sections 4.3 and 6. Neither changes behavior, but both will send a careful implementer to the wrong page.

What the March notice and the spec each say about empty fields

The EPC notice says empty API fields are no longer allowed. The specification itself expresses this as a minimum length of one character for every text type and no leading whitespace, with no separate clause. These agree in effect. They are listed here because anyone searching the spec for the word "empty" will not find the rule.

Key Takeaways

Principles

  1. VoP is a blocking dependency on another institution. It sits between data entry and authorization, so its latency is your customer's latency.
  2. Model four states, never a boolean. Match, Close Match with a name, No Match and Not possible each need their own screen, record and signal.
  3. The five seconds are yours to measure. One clock on the requesting side covers routing, handshakes, brokers and the matcher. Engineer for the one second preference.
  4. Silence is a product surface. A timeout becomes a mandated explanation and warning; late answers are discarded, not used to quietly upgrade the screen.
  5. Brokers move packets, not obligations. An RVM adds a hop to your budget and nothing to your liability shield.
  6. Identity is a certificate plus a directory entry. Your QWAC, your BIC and the EDS must agree, and the EDS copy you hold is a security dependency.
  7. Evidence is the liability shield. Keep the result, the text shown and the payer's choice, joined to the payment by request ID, and nothing more.
  8. Errata are releases. Empty fields, the character floor and trailing zero timestamps fail a fraction of traffic on some routes. Test against the spec's own patterns.
  9. Spurious warnings are a fraud cost. Every avoidable NOAP and NMTC trains payers to ignore the one that matters.

In this catalog