Verification of Payee at Scale: Architecture for Sub-Five-Second Name Matching

Verification of Payee has to return an answer while the payer is still looking at the screen, inside the same clock the instant transfer itself runs against. That single constraint turns name matching from a data problem into an architecture problem.

Most of what gets written about Verification of Payee treats it as a matching-quality question: how clever the fuzzy-match logic is, how it handles nicknames and legal forms. That work matters, but it is not what breaks first in production. What breaks first is time. VoP sits directly in the path of a payment the regulation expects to settle in under ten seconds, and the scheme gives the verification exchange a budget measured in single-digit seconds. Build the matcher without designing for that budget and you will ship something correct that is also, on a bad day, too slow to be allowed to answer.

The clock you are actually racing

Two numbers set the envelope. The Instant Payments Regulation, Regulation (EU) 2024/886, defines an instant credit transfer as one executed in under ten seconds, at any hour on any calendar day. Verification of Payee is the check that runs before that, when the payer keys in an IBAN and a name. The EPC Verification of Payee scheme rulebook sets the maximum execution time for the requesting PSP to receive the VoP response at five seconds, with a stated preference for one second or less.

Five seconds sounds generous until you decompose it. That budget has to cover the outbound directory lookup that finds the payee bank’s VoP endpoint, a network round trip to another institution you do not control, that institution’s own matching compute, and your handling of the reply — plus whatever margin you want before you breach. The obligation has been live for euro-area PSPs since 9 October 2025; non-euro member states have until 2027, but the architecture is the same. Treat the five seconds as a hard service-level objective owned by the platform, not an aspiration owned by the matching team, and everything downstream gets easier.

Where the time actually goes

Split the path into the part you own and the part you do not. When your customer is the payer, you are the requesting PSP: you normalise the input, route to the payee’s PSP, and interpret the answer. When someone else’s customer is paying yours, you are the responding PSP: you receive a name and an IBAN, look up your own account record, run the match, and reply. The responding side is the part you can make fast deterministically, because every input is yours. The requesting side is the part exposed to another firm’s latency and availability, which is where your timeout and fallback design earns its keep.

The instinct here is the same one that moves sanctions screening off the per-transaction critical path: precompute and cache everything that does not have to be computed live, so the synchronous path carries the minimum. For VoP the account record and its normalised form are yours to precompute; the counterparty’s answer is not.

The matching decision is a latency decision

The scheme recognises four outcomes: match, close match, no match, and verification not possible. The EPC’s matching recommendations do the useful work of telling you what to normalise before you compare anything — fold case, convert diacritics to the SEPA base character set, strip honorifics and titles, trim whitespace. That normalisation is cheap and it is the single biggest lever on both accuracy and speed, because it collapses the space of inputs you then have to reason about.

Close match is where cost creeps in. A Levenshtein-style edit distance handles the common cases the recommendations describe — a single spelling slip, a transposed letter pair, an initial instead of a full first name. Legal persons warrant stricter tolerances than natural persons, because the fraud surface is larger and the names are more structured. Identification codes such as an LEI or a VAT number are a special case: they resolve to match or no match only, never close match. Keep the comparison itself O(n) per candidate and bounded; the moment your matcher starts doing phonetic passes and multi-algorithm scoring on the synchronous path, measure it, because that is where a correct answer quietly slides past the budget. How those four outcomes then land in front of the payer is a customer-journey decision in its own right.

Caching without answering a question you were never asked

Cache the things that are stable and yours. Normalised forms of your own account names cache indefinitely, invalidated on account change. Directory routing — which endpoint serves a given payee bank — is stable for hours and should be cached with a modest time-to-live rather than looked up cold every time. What you must not do is cache another firm’s verification result and replay it as if it were fresh: the payee name behind an IBAN can change, and a stale affirmative is exactly the failure VoP exists to prevent. If you cache anything on the requesting side, cache the routing, not the verdict, and hold nothing that would make you the accidental custodian of counterparty personal data.

import time
import unicodedata
from functools import lru_cache
from rapidfuzz.distance import Levenshtein

HONORIFICS = {"mr", "mrs", "ms", "dr", "prof"}
BUDGET_MS = 4000  # headroom inside the scheme's 5s maximum execution time

def normalise(name: str) -> str:
    """Fold diacritics to the SEPA base set, drop case and honorifics."""
    decomposed = unicodedata.normalize("NFKD", name)
    stripped = "".join(c for c in decomposed if not unicodedata.combining(c))
    return " ".join(t for t in stripped.lower().split() if t not in HONORIFICS)

@lru_cache(maxsize=100_000)
def _norm_cached(name: str) -> str:
    return normalise(name)

def match_tier(submitted: str, registered: str, legal_person: bool) -> str:
    a, b = _norm_cached(submitted), _norm_cached(registered)
    if a == b:
        return "MATCH"
    threshold = 1 if legal_person else 2   # stricter for legal-person names
    if Levenshtein.distance(a, b) <= threshold:
        return "CLOSE_MATCH"
    return "NO_MATCH"

def respond(request, account_lookup) -> dict:
    """Responding-PSP path: our data only, so it is deterministically fast."""
    started = time.monotonic()
    record = account_lookup(request["iban"])          # your own account store
    if record is None or (time.monotonic() - started) * 1000 > BUDGET_MS:
        return {"status": "VNAV"}                      # verification not possible
    return {"status": match_tier(request["name"],
                                 record["name"],
                                 record["legal_person"])}

The directory dependency, and what happens when it is slow

On the requesting side your latency is hostage to a lookup and a hop you do not own. Keep persistent, pooled connections to the routing and verification mechanism rather than establishing a new one per request; a cold TLS handshake inside a five-second budget is waste you can design out. Set an explicit timeout well short of the maximum, and decide in advance what you return when it fires. The honest answer is “verification not possible” — a degraded but defined outcome the payer can act on — not a spinner that runs the clock down. A matcher that returns a clean fallback at 3.5 seconds beats one that returns a perfect answer at 6. This is capacity planning as much as coding, and it belongs in the same reachability and capacity work the rest of the SEPA Instant obligation demands.

The trap to avoid is building VoP as a feature bolted onto the payment flow rather than a responder with its own latency budget, its own cache tiers, and its own graceful-degradation rule. Get that framing right and the matching logic is the easy part. Get it wrong and the cleverest name matcher on the market becomes the thing that made your instant payment late.

Build and rescue work

Hands-on delivery of this kind is handled by Sixteen Pillars Studio.

Free interactive tool

Website compliance checklist

What your site has to do, based on what it actually does

Answer as much or as little as you like — the list builds as you go. Nothing is stored against your name and no email is required.

Can you trust the architecture you have?

Architecture diagrams rarely show the reality of how systems actually operate. An independent review establishes what is really there.