Skip to main content
client.setupBuy() is deprecated. Use client.setupBuyButton() with headless: true instead. It runs the same purchase with no visible UI and sends the same events. Existing setupBuy() calls keep working. Build new integrations with setupBuyButton().
Mount a hidden frame into your UI and execute a transaction from a quote signature. The frame renders no visible UI. It drives the buy pipeline in the background and emits events you handle from your own purchase screen. MoonPay serves it as the Buy Button frame in headless mode.
The frame is headless. When it emits a challenge event, render a separate challenge frame with client.setupChallenge() at the URL from the event payload. See the Handle challenges guide for the full flow.
Setup buy

Parameters

This method does not require a separate auth token. The client uses stored credentials from an active connection.

BuyEvent

onEvent receives events as the buy pipeline progresses. Use event.kind to decide how to handle each event.

FrameTransaction

This is the transaction object returned when the buy pipeline completes. FrameTransaction is a discriminated union — the failure variant carries failureCode and failureReason, the non-failure variant always carries id. Pass id to client.getTransaction() to poll for the final status.

BuyChallengePayload

BuyEventError

Result

client.setupBuy() returns a Result<BuyFrame, SetupBuyError>.

Result envelope

Result<BuyFrame, SetupBuyError>

BuyFrame

SetupBuyError

Full example

The following example walks through the full card-payment flow: get a quote for a stored card, start the purchase in a hidden frame, hand off to a challenge frame when verification is required, and dispose of the frame when the transaction completes.
Buy with a stored card
For the end-to-end card payment walkthrough — listing payment methods, adding a card, and tracking the transaction to a terminal status — see the Pay with card guide. For details on the challenge flow, see Handle challenges.

Bank transfer

Bank-transfer payments (SEPA for EUR) use the same headless flow as cards. You fetch a bank-transfer quote, call setupBuy() with its signature, and read the transaction id from the complete event. The difference is what happens after the transaction is created: the customer sends the funds from their bank. Fetch the transaction with the id and show the customer the bank details from bankTransferDepositInfo, including the payment reference, and the amount to send from source.amount. Bank transfers are a floating payment method, so the amounts are estimates until the transfer settles. The quote returns exchangeRateType: "floating"; render the estimated crypto amount with a tilde (for example, ~0.2345 BTC) and tell the customer the final amount is set when their funds settle. See Exchange rate type. For the full walkthrough, including confirming availability, rendering the deposit details, handling requotes and cancellations, and tracking the transaction to a terminal status, see the Pay with bank transfer guide.
Buy with a bank transfer
The customer must include the payment reference with their bank transfer. Transfers sent without it are rejected. Surface this prominently in your own UI, for example, “Always include your payment reference or your transfer will be rejected.” See Bank-transfer deposit details.
types.ts