Skip to main content
Use this guide to execute a bank-transfer transaction after you have a connected customer. You get a quote, execute it through the same headless Buy frame you use for cards, then render the deposit details so the customer can send funds from their bank. MoonPay runs the payment pipeline; you own the purchase UI and the deposit screen. Bank transfers support SEPA (EUR) for centralized assets. They don’t cover DeFi assets — offer a card or wallet method for those. See the Going Live section for requirements you must meet before taking this integration to production.

Prerequisites

  • A MoonPay account with bank transfers enabled. Contact your MoonPay account team to enable it.
  • A connected customer (via client.getConnection() or client.connect()).
  • A UI surface where you can render MoonPay frames (iframe on web, or WebView on mobile).
  • A destination wallet address for the purchased crypto.

Flow overview

1

Confirm bank transfer is available

Fetch the customer’s available payment method types and check for sepa.
Show bank transfer in your payment method picker only when a sepa config is present, availability.active is true, and capabilities.requiresWidget is false. A requiresWidget: true bank transfer isn’t eligible for the headless flow and must complete in the MoonPay widget instead.SEPA doesn’t create a stored payment method, so it never appears in paymentMethods. That array still lists the customer’s stored cards, so it can be non-empty even when the customer pays by bank transfer.
2

Get a bank-transfer quote

Request a quote with paymentMethod.type set to "sepa".
Bank transfers are a floating payment method: the quote returns exchangeRateType: "floating" and the crypto amount is an estimate until the customer’s funds settle. Render the estimated amount with a tilde (for example, ~0.0234 ETH) and tell the customer the final amount is set at settlement. See Exchange rate type.Show the contextual quote summary and fee breakdown before the customer confirms. See Going Live for the canonical requirements. Monitor expiresAt and refresh the quote before it expires.
3

Execute the transaction

Call client.setupBuy() with the quote signature, exactly as you do for cards. The headless frame creates the transaction and emits complete with the transaction id. For the frame URL, parameters, and events, see the Buy frame reference.
setupBuy
A bank transfer can still require a challenge, such as a KYC step-up. Handle it exactly as for cards, but route the result back into the deposit flow: the Challenge frame creates the transaction and emits it on complete, so call showDepositDetails with that transaction id. See Handle challenges.
Challenge handling
4

Read and render the deposit details

Call client.getTransaction() with the id. For a bank transfer, the response includes a bankTransferDepositInfo object with the account details and the payment reference, plus the exact fiat amount to transfer in source.amount (the gross amount, including fees). Render these fields in your own UI so the customer can pay from their banking app. MoonPay does not render the deposit UI.
Read deposit details
bankTransferDepositInfo contains:
The customer must include the payment reference with their bank transfer. Transfers sent without it are rejected. Surface it prominently, for example, “Always include your payment reference or your transfer will be rejected.” MoonPay uses the reference to match the incoming transfer to this transaction.
5

Track the transaction

A bank transfer can take from a few minutes to a few days, and the transaction stays pending until settlement. While it’s pending, the transaction’s destination.amount is 0, so keep rendering the estimate from the original quote’s destination.amount with a tilde, and make clear the crypto amount is only an estimate until the transfer lands. After settlement, switch to the transaction’s final destination.amount.Poll client.getTransaction() to track status. Because settlement can run for days, you can also subscribe to the transaction-updated webhook instead of long-lived polling.
Track the transaction
Bank-transfer transactions have the following statuses:
  • Pending: The transaction is waiting for the customer’s deposit, or the deposit has arrived and is being processed.
  • Complete: The funds have been received and the crypto delivered to the destination wallet.
  • Failed: The transfer timed out or was cancelled.
The status stays pending from the moment the transaction is created until it settles, so it doesn’t tell you whether the deposit has arrived yet. For finer-grained progress, read the stages array: each stage has a kind and a status. When the waiting_payment stage’s status is "success", the customer’s deposit has arrived. Use that to move your UI from awaiting-transfer to processing before the transaction reaches a terminal status.
Requotes and cancellations for bank transfers are handled by MoonPay’s hosted flow, not the headless flow. If the settled amount differs from the estimate, the customer is emailed and completes the requote there. A cancellation surfaces to your app as a failed transaction when you poll client.getTransaction().
Polling is the documented way to track status. If you already consume MoonPay webhooks, you can also use the existing transaction-updated webhook. See the webhooks overview.