Plaid logo
Docs
ALL DOCS

Payments (Europe)

  • Payments (Europe)
  • Payment Initiation
  • Variable Recurring Payments
  • Payment Status
  • Reconciliation
  • Payments Dashboard
  • Virtual Accounts and Payouts
Plaid logo
Docs
Plaid.com
Log in
Get API Keys
Open nav
Close search modal
Ask Bill!
Ask Bill!
Hi! I'm Bill! You can ask me all about the Plaid API. Try asking questions like:
    Pssst -- I also moonlight as your IDE's research librarian! Plug me in via the Plaid MCP Server.
    Note: Bill isn't perfect. He's just a robot platypus that reads our docs for fun. You should treat his answers with the same healthy skepticism you might treat any other answer on the internet. This chat may be logged for quality and training purposes. Please don't send Bill any PII -- he's scared of intimacy. All chats with Bill are subject to Plaid's Privacy Policy.

    Reconciliation

    Match funds arriving in your account against the payments you initiated

    Plaid initiates a payment from your end user's bank account, but the funds arrive in your own account, on the payment rails. Plaid has no visibility into the payment after the payer's bank accepts it, so if you are not using Virtual Accounts, matching an arriving credit back to the payment you created is your responsibility.

    This guide covers which identifiers to match on, when each becomes available, and an approach to matching that works across UK and European institutions. Use it as a starting point rather than a complete specification: the right design depends on your ledger, your volumes and the institutions your end users pay from, and no matching logic will attribute every credit automatically.

    If you use Virtual Accounts, Plaid performs this reconciliation for you and reports the result as PAYMENT_STATUS_SETTLED. See Payment Confirmation. The rest of this guide applies to payments arriving directly in your own bank account.

    The three identifiers

    A payment carries three identifiers, and they serve different purposes. Using the wrong one for reconciliation is the most common source of unmatched payments.

    • payment_id identifies the payment to the Plaid API. Use it to call /payment_initiation/payment/get and to look up your own records when a PAYMENT_STATUS_UPDATE webhook arrives. It is never transmitted on the payment rails, so it will not appear on the credit that lands in your account.

    • end_to_end_id is an identifier Plaid generates for every payment and asks the payer's bank to carry on the underlying payment message. Where the bank does so, it arrives on the credit as the ISO 20022 EndToEndId and is usually the most precise key available. Plaid generates a unique value for each payment, but the value that arrives on a credit is not guaranteed to be unique: some banks overwrite it, send a default placeholder value, or carry it over onto a later payment. Treat a match on it as strong evidence to confirm, not as proof.

    • reference is the value you supply when creating the payment. It is presented to the payer during authorisation and normally appears on the credit as the remittance information.

    When each identifier becomes available

    end_to_end_id is assigned by Plaid during the /payment_initiation/payment/create call itself, before your end user has selected an institution. Calling /payment_initiation/payment/get immediately after creating a payment will return it, and its value never changes afterwards. There is no need to poll for it, and no need to wait until after the Link flow completes.

    This matters because a payment can settle while your end user is still in the Link session. Store end_to_end_id and reference as pending keys before you launch Link, and a credit that arrives early is already matchable.

    adjusted_reference is the exception. If Plaid has to modify your reference to satisfy the payer's bank, the adjusted value is written when the bank acknowledges the initiation, and it is null until then. It is settled once the payment reports PAYMENT_STATUS_INITIATED or PAYMENT_STATUS_EXECUTED, and it is never populated after that. Key this check on either status rather than on INITIATED alone: some payments report EXECUTED without ever reporting INITIATED, and others terminate at INITIATED without reaching EXECUTED. See Payment Status for how the statuses behave across markets.

    Once a payment has reported either status, a null adjusted_reference means your reference was not adjusted, not that the value has yet to arrive.

    A suggested matching approach

    1. Create the payment with a reference that is unique per payment. This does more for match rates than any other step: a unique reference is what makes the fallback path reliable, and it also avoids Plaid having to rewrite a reference that collides with one you have used before.

    2. Call /payment_initiation/payment/get immediately, and store payment_id, end_to_end_id and reference. Index the last two, because they are what you will match on.

    3. Launch Link. From this point a matching credit may arrive at any time.

    4. On the PAYMENT_STATUS_UPDATE webhook for PAYMENT_STATUS_INITIATED or PAYMENT_STATUS_EXECUTED, store adjusted_reference if it is populated. The webhook also carries original_reference and adjusted_reference directly, so you can record it without an additional API call.

    5. When a credit arrives in your account, a reliable order to attempt matches is:

      • Compare the incoming EndToEndId against your stored end_to_end_id, case-insensitively. Not all banks preserve the original case, so normalise both values before comparing. Confirm the match with the amount and currency, and if they differ, treat the ID as not matching and continue to the reference comparison.

      • If the credit carries no EndToEndId, or it does not match, compare the incoming remittance information against adjusted_reference if it is set, otherwise reference. Confirm the match with the amount and currency.

    Compare on end_to_end_id first because it is precise when present and costs nothing to attempt. Build the reference comparison to carry real volume rather than treating it as an error branch — for a significant share of European payments it is the only key that will match.

    Reference requirements

    Your reference must be 1–18 characters and alphanumeric. Individual institutions impose tighter rules than that, and where your reference does not satisfy them Plaid adjusts it before sending and returns the adjusted value as adjusted_reference. Both the original and the adjusted value are returned by /payment_initiation/payment/get, and you should store both.

    The most common adjustment is truncation, where an institution accepts fewer characters than you supplied. It is not the only one: some institutions enforce a minimum length or disallow leading spaces, in which case the reference is padded or rewritten rather than shortened.

    Because the adjusted reference is what the payer's bank actually sends, always match on adjusted_reference when it is set. Keeping your reference short, unique, and free of spaces and punctuation reduces how often any adjustment is needed.

    When a credit does not match

    However good your matching logic is, some credits will not match a pending payment, some will match a payment they do not belong to, and some payments will never be matched by a credit at all. Plan for all three before you go live, because the first occurrence usually arrives without warning.

    Credits that match no payment

    A credit can arrive that matches nothing you are expecting. Common causes are an institution changing the reference in a way that adjusted_reference does not reflect, an end user who set you up as a payee manually and never used the Plaid flow, or a payment made to you outside Plaid altogether. Route these to an unmatched queue rather than discarding them or attributing them on a best guess, and keep the raw remittance information, amount and payer details so they can be resolved later.

    Repeated payments

    Once an end user has paid you, most banking apps save you as a payee and offer to repeat a previous transfer. A repeat can arrive carrying the same reference, the same amount and the same payer account as a payment you have already matched. No Plaid flow ran, so there is no new payment_id and no PAYMENT_STATUS_UPDATE webhook.

    Matching on end_to_end_id does not protect you here. Depending on the payer's bank, a repeat may carry a freshly generated EndToEndId, none at all, or a copy of the one Plaid issued for the original payment — in which case it will match your stored end_to_end_id exactly, even though it is a different payment.

    Guard against this by treating each payment as matchable only once, on both match paths. After a pending payment has been matched and acted on, a later credit carrying the same values is a new, unattributed credit rather than a re-delivery of the old one. When an incoming EndToEndId resolves to a payment you have already reconciled, do not fall back to reference matching for that credit either — doing so would attribute it to a different payment.

    Payments whose credit never arrives

    Neither PAYMENT_STATUS_INITIATED nor PAYMENT_STATUS_EXECUTED confirms that funds have reached your account. If a credit for a pending payment has not arrived within the window you would expect for its scheme — typically seconds to minutes on Faster Payments and SEPA Instant, and up to a few business days on SEPA Credit Transfer — flag it for investigation rather than leaving it pending indefinitely. If you act on a payment before its credit is matched, decide in advance how you will handle one whose credit never arrives.

    Deciding what to do

    What you do with an unattributed credit is a policy decision that Plaid cannot make for you — credit the end user's balance, hold the funds for manual review, or return them to the sender. Expect a residual share of credits to need manual review, and size your operations for it rather than treating it as an exception path.

    If you use Virtual Accounts, Plaid detects payments that arrive without an associated Plaid payment and can refund them automatically. See Handling Unexpected Payments.

    Regional differences

    The identifiers and the matching approach are the same in every market Plaid supports, but how reliably end_to_end_id reaches the creditor is not.

    • United Kingdom. Payments run on Faster Payments, and the identifier reaches the creditor on the large majority of payments. Reference matching is still required as a fallback.

    • Europe. Payments run on SEPA Credit Transfer or SEPA Instant Credit Transfer. Propagation of the identifier varies considerably by institution and by market — some banks carry it on effectively every payment, while at others it rarely arrives. In several European markets the reference is the only key that will match most payments, so the fallback path is not optional.

    end_to_end_id is nullable and its presence on an arriving payment is not guaranteed. If testing shows that a particular institution carries it reliably, that is a reasonable optimisation to lean on, but treat it as an observation rather than a contract — institutions change this behaviour, in both directions, without notice. Keep the reference fallback active even for institutions that currently look reliable, and do not raise alerts on a missing EndToEndId.

    Testing your matching logic

    You can exercise the full flow in Sandbox by following the one-time payment integration guide. Sandbox assigns end_to_end_id at creation in the same way Production does, so a /payment_initiation/payment/get call straight after creating a payment will return it, and you can confirm that you are storing and indexing both keys before launching Link.

    Sandbox cannot reproduce the behaviour of a specific bank omitting the identifier from the arriving credit. Test your fallback path by matching on the reference with the end_to_end_id comparison disabled, so you know the reference path works on its own before you go live.

    Developer community
    GitHub
    GitHub
    Stack Overflow
    Stack Overflow
    YouTube
    YouTube
    Discord
    Discord