Multicurrency

Multi-currency guide

Run GBP and EUR accounts side by side. Understand the differences between payment rails, manage balances across currencies, and build integrations that handle both.

How Mercury handles currencies

Mercury supports two currencies: GBP and EUR. Each has its own account type, payment rail, and set of endpoints. A single client can hold one account per currency, managed independently but queryable together.

GBPSterling
→Accounts: /api/v1/sterling-account
→Payments: /api/v1/payments (Faster Payments)
→AccountType: GB
→Max payment: £1,000,000
→Returns supported
EUREuro
→Accounts: /api/v1/euro/vibans
→Payments: /api/v1/euro/payments/sepa-ct (SEPA CT)
→AccountType: MCCY (multi-currency)
→Max payment: €999,999,999.99
→No return endpoint

Account structure per client

Each client can hold one GBP and one EUR virtual IBAN. Both are provisioned independently and have separate balances. Use the client accounts endpoint to retrieve both in a single call.

Client: Acme Ltd (CLIENT-001)
GBP account

GB71CLRB04082700001159

Sort code: 040827 | Acc: 00001159

EUR account

GB85CLRB04274893940059

Sort code: 042748 | Acc: 93940059

Both use GB IBAN prefix

EUR accounts also carry a GB-prefixed IBAN (e.g. GB85CLRB...). This is correct for UK-based accounts holding EUR. Do not reject these in your IBAN validation. Differentiate by the Currency field in the response.

Provisioning both accounts

Create accounts sequentially or in parallel. Both use the same request body. Mercury determines the currency from the endpoint path.

import requests

# GBP account
gbp_response = requests.post(
    f"{BASE_URL}/api/v1/sterling-account",
    headers=headers,
    json={"clientReference": "CLIENT-001"}
)

# EUR account
eur_response = requests.post(
    f"{BASE_URL}/api/v1/euro/vibans",
    headers=headers_eur,  # re-sign for different path
    json={"clientReference": "CLIENT-001"}
)

print("GBP:", gbp_response.json())
print("EUR:", eur_response.json())
// GBP account
const gbpRes = await fetch(`${BASE_URL}/api/v1/sterling-account`, {
  method: "POST",
  headers: signHeaders("POST", "/api/v1/sterling-account", body),
  body: JSON.stringify({ clientReference: "CLIENT-001" })
});

// EUR account
const eurRes = await fetch(`${BASE_URL}/api/v1/euro/vibans`, {
  method: "POST",
  headers: signHeaders("POST", "/api/v1/euro/vibans", body),
  body: JSON.stringify({ clientReference: "CLIENT-001" })
});

Both return Status: "Processing" immediately. Listen for Accounts.AccountCreated webhooks to confirm each IBAN is ready. The EUR creation also fires Clients.ClientAssignedAccount.

Querying across currencies

Three ways to query accounts, depending on what you need:

All GBP accounts

GET /api/v1/sterling-account

Every GBP account across all your clients. Use for balance monitoring dashboards.

All EUR accounts

GET /api/v1/euro/vibans

Every EUR account across all your clients. Same response structure as GBP.

Both for one client

GET /api/v1/client/{ref}/accounts

GBP + EUR in one call. Best for client detail pages and per-client reconciliation.

[
  {
    "ClientReference": "CLIENT-001",
    "ClientName": "Acme Ltd",
    "Iban": "GB85CLRB04274893940059",
    "SortCode": "042748",
    "AccountNumber": "93940059",
    "Balance": { "CurrencyCode": "EUR", "Amount": 1000 },
    "AvailableBalance": { "CurrencyCode": "EUR", "Amount": 1000 },
    "Currency": "EUR",
    "Status": "Active",
    "ExternalReference": "EXT-REF-001",
    "CreatedDate": "2026-02-23T15:26:37Z"
  },
  {
    "ClientReference": "CLIENT-001",
    "ClientName": "Acme Ltd",
    "Iban": "GB71CLRB04082700001159",
    "SortCode": "040827",
    "AccountNumber": "00001159",
    "Balance": { "CurrencyCode": "GBP", "Amount": 1000 },
    "AvailableBalance": { "CurrencyCode": "GBP", "Amount": 1000 },
    "Currency": "GBP",
    "Status": "Active",
    "ExternalReference": "EXT-REF-001",
    "CreatedDate": "2026-02-23T15:26:37Z"
  }
]

Payment rails comparison

GBP and EUR use completely separate payment infrastructure. The endpoint, request format, operating hours, and webhook events all differ.

GBP (Faster Payments)EUR (SEPA CT)
Endpoint /api/v1/payments /api/v1/euro/payments/sepa-ct
SchemeUK Faster PaymentsSEPA Credit Transfer
Operating hours24/7Mon-Fri 06:00-14:25 UK time
Holiday scheduleUK bank holidays onlyTARGET2 (1 Jan, 1 May, 25-26 Dec)
Max amount£1,000,000€999,999,999.99
Creditor routingIBAN or account number + sort codeIBAN required (+ SWIFT if no IBAN)
CoP supportYes (optional)No
ReturnsYes (/payments/return-payment)No return endpoint
Client ref fieldreferenceclientReference
Settled webhook TransactionSettled MccyOutboundPaymentComplete

Sending GBP payments

Submit via Faster Payments. Provide the creditor by IBAN or by account number + sort code. Optionally link a Confirmation of Payee check.

{
  "reference": "CLIENT-001",
  "endToEndIdentifier": "E2E-20260414-001",
  "debtor": {
    "iban": "GB71CLRB04082700001159",
    "paymentAmount": {
      "currencyCode": "GBP",
      "amount": 500
    },
    "paymentReference": "Invoice 2026-001"
  },
  "creditor": {
    "accountName": "John Smith",
    "accountNumber": "55779911",
    "sortCode": "200000"
  }
}
{
  "Payment": {
    "PaymentId": "764b7d22-9fa0-43bb-920e-326e8634b0f8",
    "Reference": "CLIENT-001",
    "Debtor": {
      "Iban": "GB71CLRB04082700001159",
      "PaymentAmount": { "CurrencyCode": "GBP", "Amount": 500 },
      "PaymentReference": "Invoice 2026-001",
      "Name": "Acme Ltd"
    },
    "Creditor": {
      "AccountName": "John Smith",
      "AccountNumber": "55779911",
      "SortCode": "200000",
      "Iban": "GB60BARC20000055779911"
    },
    "Status": "Pending",
    "Message": "Payment has been successfully requested and is now being processed"
  }
}

Sending EUR payments

Submit via SEPA Credit Transfer. The creditor must have an IBAN. If no IBAN is available, provide a SWIFT/BIC code instead.

Operating hours

SEPA payments are only processed Monday to Friday, 06:00 to 14:25 UK time. Submissions outside this window or on TARGET2 holidays (1 January, 1 May, 25-26 December) will be rejected.

{
  "clientReference": "CLIENT-001",
  "endToEndIdentifier": "E2E-EUR-20260414-001",
  "debtor": {
    "iban": "GB85CLRB04274893940059",
    "paymentAmount": {
      "currencyCode": "EUR",
      "amount": 1000
    },
    "paymentReference": "Invoice EUR-2026-001"
  },
  "creditor": {
    "accountName": "Sender GmbH",
    "iban": "FR7630006000011234567890189"
  }
}
{
  "Payment": {
    "PaymentId": "085440ed-1326-41f4-b4a6-6188dbd42409",
    "Reference": "CLIENT-001",
    "Debtor": {
      "Iban": "GB85CLRB04274893940059",
      "PaymentAmount": { "CurrencyCode": "EUR", "Amount": 1000 },
      "PaymentReference": "Invoice EUR-2026-001",
      "Name": "Acme Ltd"
    },
    "Creditor": {
      "AccountName": "Sender GmbH",
      "AccountNumber": null,
      "SortCode": null,
      "Iban": "FR7630006000011234567890189"
    },
    "Status": "Pending",
    "Message": "Payment has been successfully requested and is now being processed"
  }
}

Returning GBP payments

You can return a received inbound FPS payment to the sender. Provide the original transaction ID and a return reason. This is only available for GBP.

{
  "transactionId": "TXN-1234",
  "returnReason": "Funds received in error"
}
{
  "Message": "Return payment successfully submitted."
}

Webhook events by currency

Account webhooks are shared. Payment webhooks are split by currency. GBP events use the Payment* prefix, EUR events use Mccy*.

GBP payment events

PaymentTransactionSettled

PaymentTransactionRejected

PaymentStatusChanged

PaymentValidationFailed

PaymentAssesmentFailed

PaymentInboundTransaction

PaymentInboundTransactionHeld

PaymentOutboundTransactionHeld

PaymentInboundInternalTransaction

PaymentInternalTransferSettled

EUR payment events

MccyOutboundPaymentComplete

MccyOutboundPaymentFailed

MccyInboundPaymentComplete

MccyInboundInternalTransaction

MccyPaymentStatusChanged

MccyInternalTransferSettled

Request body differences

The GBP and EUR payment requests are similar but not identical. Watch for these differences when building a currency-agnostic payment layer:

Client reference field name

GBP uses reference. EUR uses clientReference. If you build an abstraction layer, map this field per currency.

Creditor routing

GBP accepts IBAN or account number + sort code. EUR requires IBAN (or SWIFT as fallback). Your payment form should adapt based on currency selection.

SEPA operating hours

GBP FPS runs 24/7. SEPA CT is weekdays 06:00-14:25 UK time only. Build scheduling logic or clear user-facing messaging for EUR payments submitted outside this window.

Webhook Type prefix

GBP webhooks use Payment* prefix. EUR uses Mccy*. Your webhook handler needs separate routes or a prefix check to determine the currency context.

Architecture pattern: currency-agnostic service

If you support both currencies, consider abstracting the differences behind a unified interface:

PAYMENT_CONFIG = {
    "GBP": {
        "account_path": "/api/v1/sterling-account",
        "payment_path": "/api/v1/payments",
        "client_ref_field": "reference",
        "settled_webhook": "PaymentTransactionSettled",
        "failed_webhook": "PaymentTransactionRejected",
    },
    "EUR": {
        "account_path": "/api/v1/euro/vibans",
        "payment_path": "/api/v1/euro/payments/sepa-ct",
        "client_ref_field": "clientReference",
        "settled_webhook": "MccyOutboundPaymentComplete",
        "failed_webhook": "MccyOutboundPaymentFailed",
    },
}

def send_payment(currency, client_ref, debtor_iban, amount, reference, creditor):
    config = PAYMENT_CONFIG[currency]
    body = {
        config["client_ref_field"]: client_ref,
        "endToEndIdentifier": generate_unique_id(),
        "debtor": {
            "iban": debtor_iban,
            "paymentAmount": {"currencyCode": currency, "amount": amount},
            "paymentReference": reference,
        },
        "creditor": creditor,
    }
    path = config["payment_path"]
    headers = sign_request("POST", path, body)
    return requests.post(f"{BASE_URL}{path}", headers=headers, json=body)

def handle_webhook(event_type, payload):
    for currency, config in PAYMENT_CONFIG.items():
        if event_type == config["settled_webhook"]:
            return mark_payment_settled(payload, currency)
        if event_type == config["failed_webhook"]:
            return mark_payment_failed(payload, currency)

Did this page help you?