All entries
Chapter VIII
Journal · 04 Sept 2026 · 9 min read

Verification of Payee API: What Actually Breaks

A working guide to the EPC VoP API: the four match codes, the BIC11 routing rule, the 5-second ceiling, and the timestamp format that fails silently.

Close Match does not exist on the identification-code path in Verification of Payee. Send a company's LEI alongside an IBAN and CMTC is unreachable, because version 1.1 of the scheme rulebook drops it and files the deletion under TYPO. If your switch statement still has that branch, it is dead code.

That is the character of this scheme. The headline is a name check on a bank transfer, which sounds like a solved problem. The cost sits in a handful of format rules and routing details that pass staging happily and fail against a counterparty PSP in production. This is what the EPC specifications actually require, and where integrations break.

What Is Verification of Payee and Who Has to Support It?

VoP is a mandatory pre-transfer check: before a euro credit transfer executes, the payer's PSP asks the payee's PSP whether the name given matches the account holder on that IBAN. It is not fraud scoring and not a payment message. It is one question, asked synchronously, answered on the name path in one of four ways.

The obligation comes from the Instant Payments Regulation, which inserted Article 5c into Regulation (EU) 260/2012. PSPs in euro-area member states had to comply by 9 October 2025; PSPs in non-euro EEA states have until 9 July 2027. Article 5c(8) puts the liability on the PSP that fails to perform the check, not on the customer who ignores the result, which is the opposite of how the UK regime allocates blame. As of April 2026 roughly 2,770 PSPs had adhered to the scheme, alongside 58 routing and verification mechanisms acting on their behalf.

What Changes in Verification of Payee v1.1 on 20 September 2026

Three documents take effect together on 20 September 2026 at 03:30:00.000 CET, and all three bind every adhering PSP contractually:

DocumentReferenceVersion
VOP Scheme RulebookEPC218-231.1
Inter-PSP API SpecificationsEPC103-241.1.1
API Security FrameworkEPC164-222.1

The API Specifications and the Security Framework are binding supplements rather than guidance, so there is no partial adoption. The third applies to SRTP and SPAA participants as well, making it one certificate estate change with three schemes hanging off it.

The substantive deltas against v1.0.1:

  • Empty strings are now invalid. The spec introduced scheme-specific text types (Max35TextVop, Max140TextVop, Max256TextVop and friends) carrying minLength: 1 and a pattern rejecting leading whitespace. A field you previously sent as "" rather than omitting is a 400.
  • BIC11 only, in the responding agent (AT-C002) and requesting agent (AT-D002) attributes.
  • Close Match removed from the identification-code result set.
  • Timestamp and Content-Type handling clarified, including alignment on application/problem+json for errors.

Why BIC8 Now Fails VoP Routing

This is the change most likely to page someone, and it breaks two things in two different ways.

Routing first. You do not query a central directory per transaction. The EPC Directory Service publishes a full local file daily, EDS-VOP-V1-DAILY-F- in XML, JSON and CSV, and every participant builds its own lookup from it. The path runs IBAN, then the account-holding institution's BIC, then the accountHoldingBic row, then apiUri with priorityNumber 1 as primary and 2 as backup. Both BIC fields are declared as 11 characters, so an eight-character head-office BIC matches no key and you get no route rather than an error you can read.

Authorisation second, and this one is nastier because the request is well formed. The responding PSP authenticates you from your PSD2 QWAC, pulls the National Authorisation Number from the certificate, then checks that the NAN and the BIC in your request body are a pair it can find in the directory. A BIC8 in requestingAgent.financialInstitutionId.bicfi fails that pairing and returns 401 CLIENT_INCONSISTENT. The symptom reads like a certificate problem. It is a padding problem: branch-less BICs need XXX on the end, which is why every example in the specification looks like ABCDBEBBXXX.

What a VoP Response Actually Returns

One endpoint, POST /vop/v1/payee-verifications, and a deliberately small response. The scheme borrows ISO 20022 element names and code lists but is not an ISO 20022 message; there is no pacs or acmt identifier anywhere in it.

{
  "party":          { "name": "Dupont Jean" },
  "partyAccount":   { "iban": "BE12345678901234" },
  "partyAgent":     { "financialInstitutionId": { "bicfi": "ABCDBEBBXXX" } },
  "requestingAgent":{ "financialInstitutionId": { "bicfi": "ABCDBEB0XXX" } }
}

A close match comes back as two fields and nothing else: { "partyNameMatch": "CMTC", "matchedName": "Dupond Jean" }.

partyNameMatch takes MTCH, NMTC, CMTC or NOAP. partyIdMatch takes only MTCH, NMTC or NOAP, and that is the v1.1 deletion. matchedName must be present if and only if the result is CMTC, so a bare CMTC is a scheme violation rather than a null to shrug at.

Two NOAP behaviours on the ID path are easy to get wrong. Code type supported at directory level but not held for that payee gives NOAP with HTTP 200. Code type not supported at directory level at all gives HTTP 400. One conceptual outcome, two branches of your client.

Errors are RFC 7807 application/problem+json, the code list is four values (FORMAT_ERROR, CLIENT_INVALID, CLIENT_INCONSISTENT, TIMESTAMP_INVALID), and HTTP status is restricted to 200, 400, 401 and 500. If your error handling branches on 403, 429 or 503, the scheme never produces them.

Why VoP Rejects a Timestamp That Looks Valid

Here is the detail no vendor summary carries. The X-Request-Timestamp format forbids trailing zeroes in the milliseconds component. 2025-07-10T14:36:25.46Z is valid. 2025-07-10T14:36:25.460Z is not. 2025-07-10T14:36:25Z is valid, and .000Z is not.

Most standard library formatters emit fixed three-digit milliseconds. Go's RFC3339Nano strips trailing zeros; a .000 layout does not. Java's Instant.toString() drops them; a hand-rolled SimpleDateFormat pattern emits .460 all day. The result is 400 TIMESTAMP_INVALID on roughly one request in a thousand, whenever the clock lands on a round millisecond, which is the shape of bug that survives a test suite and surfaces as an unexplained error rate weeks later. Both Z and a numeric offset are accepted; a timestamp from a previous day is a hard 400.

How Long Does a VoP Check Have to Answer?

Five seconds, with the rulebook expressing a preference for one second or less. Miss it and you must tell the requester immediately that the check could not be performed, you may offer a retry, and if the response then turns up late you are required to discard it.

That clock is per request, and there is no bulk request to amortise it across. In the inter-PSP space a request concerns a single account-number-and-name combination, so a customer file of 400 suppliers is 400 separate POSTs. A change request asked for a bulk-specific execution time and the working group recommended rejecting it: there is no such thing as a bulk request on the wire. The only bulk rule with legal force is the Instant Payments Regulation's opt-out for non-consumer users submitting a package of payment orders.

VoP vs UK Confirmation of Payee: What Actually Differs

If you run both, the temptation is to normalise them into one internal result type. Do that carelessly and you will lose money on close matches.

EU VoPUK Confirmation of Payee
Result shapeFour-value enum on the name pathMatched boolean plus ReasonCode when false
Close matchCMTC, its own stateMBAM, and Matched is false
Account identifierIBAN14-digit sort code plus account number
Secondary referenceunstructuredRemittanceInformation, advisory inputSecondaryIdentification, a real lookup key; unresolvable returns IVCR
RoutingDaily EDS local file, BIC11 to URIOpen Banking directory
Response time5 seconds, published in the rulebookResponseWithinSla boolean; threshold not public
Legal hookIPR Article 5c(8): the PSP is liable for not performing the checkPSR Specific Direction 17, separate from APP reimbursement

The close-match row is the trap. A UK-shaped if (!matched) block() ported to VoP either blocks every CMTC, or maps it to matched and silently waves through the exact population the check exists to catch. Pay.UK puts mismatched-name transactions at 25 times more likely to be fraudulent and credits CoP with a 59% fall in wrong-account claims on Faster Payments, across 320-plus PSPs and over two million checks a day. That value sits almost entirely in the close-match band.

One asymmetry cuts the other way: VoP publishes its five-second ceiling, while CoP's SLA arrives as a boolean whose threshold you cannot look up. For payout flows where a name check decides whether funds move, a ceiling you can size a timeout against is the more useful property.

My Read: NOAP Is the Number That Will Embarrass the Scheme

An opinion, marked as one. The interesting metric after 20 September is not match rate. It is NOAP rate, and nobody can currently explain it.

NOAP is a single opaque value. The rulebook tells the responding PSP to provide an accurate reason code and points at the API specification; the specification contains no such list. Closed account, blocked account, out of scope, deceased customer and plain "we could not look it up" collapse into the same four characters. You cannot separate a transfer worth retrying tomorrow from one whose beneficiary account no longer exists.

The v2.0 working group has proposed ISO codes to fix this: AC01 incorrect account number, AC04 closed account, AC06 blocked account, MD07 deceased, TD01 no data available. Two caveats. It is a recommendation pending a scheme-board decision, and it is proposed as optional, which in an interoperability scheme means the codes turn up precisely where you did not need them. Rulebook v2.0 publishes around November 2026 and takes effect a year later, so November 2027 opt-in is the earliest realistic date.

My prediction: by mid-2027 the aggregate NOAP rate is the number regulators start asking about, because it is the one place a PSP can technically comply while returning nothing useful, with no obligation to explain it.

Scope runs the other way. The agreed PSR text carries an Article 50 extending verification to credit transfers outside the SEPA Regulation, with a fallback for accounts not identified by IBAN. Build your name-check abstraction on the assumption that the identifier is not permanently an IBAN.

What This Means for Payment Developers

Concretely, if you own a VoP integration:

Grep for BIC8 first. Any path building requestingAgent or partyAgent from a stored institution record needs padding to 11. Assert on length at construction, not at send: the failure is a 401 that looks like certificate rotation and sends you to the wrong team. Test the round millisecond. Format an instant with zero milliseconds and assert the output matches the scheme regex. Two lines, for a bug otherwise invisible until it is a support ticket. Make CMTC a first-class state. Not a boolean, not an enum you flatten on the way in. Whatever the product does with a close match, that decision belongs above the client library. And delete the CMTC branch on partyIdMatch: under v1.1 it is unreachable code that lies to the next person reading it. Run both roles through the reference toolbox. The EPC's API Reference Toolbox v1.1 carries 41 requesting-side and 50 responding-side negative cases: missing X-Request-ID, missing response timestamp, CMTC without matchedName. Treat the daily EDS file as a production dependency. Cutover windows in startDateTime and endDateTime, a primary and backup URI per participant, changing daily. A stale copy is a routing outage with no error attached.

Deadlines like this one rarely fail on the headline change. They fail on a padded identifier and a formatter's default precision, the same way the ISO 20022 address mandate failed on fields nobody modelled properly. For more of the settlement and reconciliation work behind posts like this, start at Tom Wang.

Check the BIC first. It is the cheapest thing on the list and the one that fails least legibly.