Skip to main content
The Para Modal’s built-in Buy/Withdraw flow lets users on- and off-ramp without any code. If you’d rather drive the ramp from your own UI — a “Buy” button, a funding CTA, a checkout step — use the useInitiateFiatRamp hook to launch the same flow with the amount, asset, and provider you choose.
useInitiateFiatRamp requires @getpara/react-sdk 3.18 or later.
Which providers, assets, and networks are available — and their API keys — are configured in the Developer Portal, not in code. Set those up first: see Set Up Fiat Onramps (and the Coinbase-specific steps on that page). The hook only launches the flow; it can’t offer a provider or asset you haven’t enabled.

Basic usage

useInitiateFiatRamp returns a mutation-style handle. Call initiateFiatRamp(params) from a click handler to launch the flow:
Providers open in a popup, so initiateFiatRamp must be called within the user’s click gesture (as above) — calling it later, e.g. from a useEffect or a timeout, will be blocked by the browser’s popup blocker.

Choosing the destination wallet

By default the ramp targets the user’s active wallet. Pass a wallet explicitly to target a different one.
Omit the wallet fields — the on-ramp funds whichever wallet is currently active.

Choosing the provider

Omit provider to let Para pick the first enabled provider that serves the asset + amount, or name one explicitly. Use the OnRampProvider enum:
For Coinbase, pass provider: OnRampProvider.COINBASE and pick the surface with method:

Buying an exact token amount

Pass tokenQuantity instead of fiatQuantity to buy a specific amount of the destination token rather than spending a set amount of fiat — useful when the user owes an exact amount (a mint price, a gas top-up, an invoice):
The widget providers (MoonPay, Ramp, Stripe, and the Coinbase widget) carry this through as an exact crypto-out buy. Coinbase inline Apple Pay is fiat-only, so there it falls back to the fiat equivalent at the current rate and the delivered amount will vary slightly with the rate.
fiatQuantity wins if you pass both. tokenQuantity applies to buys only — it’s ignored for SELL. Para converts the token amount to a fiat estimate at the current rate to check the provider’s minimum and maximum; if that rate lookup fails the amount can’t be validated, so the launch can’t go direct: the prefilled form opens with reason AMOUNT_MISSING (or throws, with throwIfIncomplete: true).

Selling (off-ramp)

Pass type: OnRampPurchaseType.SELL to launch a withdrawal instead of a purchase (for providers that support selling — see provider support):

Outcomes

initiateFiatRamp resolves to one of two outcomes:
Neither outcome returns a purchase id — see Knowing which purchase launched.
In the Buy form the buyer can change the destination wallet, the token and the amount. A request for “25 USDC on Base to wallet X” can come back as something else entirely, with nothing to signal that your intent was dropped. Where any of those are pinned — a mint price, an invoice, a specific wallet — use throwIfIncomplete rather than letting the request be quietly renegotiated.

Failing instead of falling back

Pass throwIfIncomplete: true and the hook throws OnRampLaunchError instead of opening the form. The error carries a machine-readable code alongside a message naming what blocked it:
You can also read error off the hook instead of catching, the same as any mutation. These are the same codes the form outcome reports as reason, so the handling is identical whichever mode you choose.
Nothing is persisted either way. A purchase row is written only when a purchase actually launches, so a throw leaves nothing behind — and neither does a form outcome the buyer abandons.

Knowing which purchase launched

initiateFiatRamp doesn’t return a purchase id, because when it resolves there may not be a purchase yet: a form outcome means the buyer hasn’t chosen what they’re buying. Purchases also start from the Para Modal’s own Buy button, which never goes through your call at all. So Para announces the launch as an event. Subscribe once and you’ll see every purchase in your app, whichever surface started it:
onParaEvent returns its own unsubscribe function, so returning it straight from useEffect is all the cleanup you need. ONRAMP_LAUNCHED fires once per purchase, at the moment the provider session exists and the buyer is paying — the widget popup opening, or the Apple Pay sheet appearing. purchaseId is Para’s id for that purchase: the same id that later arrives on onramp.completed, and the one you pass to getOnRampPurchase. Because it fires at launch rather than when you called the hook, the payload describes what is actually being bought, after any edits the buyer made in the form. That — not what you asked for — is the purchase to record.
OnRampEventData is the onramp.completed payload field for field, so one function can handle both the browser event and your webhook. Two fields differ by timing rather than design: transactionHash and completedAt are null at launch, because nothing has settled yet.
ONRAMP_LAUNCHED and ONRAMP_TRANSACTION_COMPLETE_V2 require @getpara/react-sdk 3.20 or later.

Tracking a purchase to completion

A launch is not a delivery. The provider still has to take payment and send the crypto, which happens well after its checkout closes — so follow the purchase by its id rather than treating the launch as the end of the story.
1

Subscribe to onramp.completed

Configure a webhook endpoint in the Developer Portal and subscribe to onramp.completed. It fires once the provider confirms delivery and carries the settlement transaction hash, the delivered token, and the same purchaseId you saw on ONRAMP_LAUNCHED.On your backend, @getpara/rest-sdk exports the payload types. Use ParaWebhookEvent rather than the generic WebhookEvent — it’s a discriminated union, so a switch on event.type narrows event.data to that event’s payload:
2

Reconcile anything you missed

Look the purchase up to read its current state — to recover from a dropped webhook, or to check one still in flight:
The response is the webhook payload plus a status of INITIATED, FINISHED, or CANCELLED. transactionHash and completedAt are null until it’s FINISHED, so branch on status rather than inferring completion from the hash, and treat an unrecognized status as non-terminal.The underlying endpoint is GET /v1/onramp/purchases/{purchaseId} if you’d rather call it directly.
getOnRampPurchase and the webhook types require @getpara/rest-sdk 3.20 or later. That package is server-side only — it sends your API key on every request, so don’t import it into browser or mobile code.

Updating the UI in the browser

For in-app feedback — a toast, a balance refresh — listen for ONRAMP_TRANSACTION_COMPLETE_V2, which carries the same OnRampEventData payload with transactionHash and completedAt now filled in:
It only fires on a settled purchase, never on the provider merely reporting that the payment cleared. That distinction matters: a card can clear and the delivery still fail, and Apple Pay checkouts show a success screen before settlement is confirmed. So treat this as a cue to refresh your UI, and keep crediting accounts off the webhook — it reaches you whether or not the buyer’s tab is still open. Unlike ONRAMP_LAUNCHED, which fires for every provider, this event depends on the provider telling the browser it settled: Ramp is the exception. Its widget reports settlement only to Para’s servers and sends nothing back to the browser, so a Ramp purchase reaches you through onramp.completed alone. Don’t gate a Ramp flow on this event; it will never arrive. You’ll still get ONRAMP_LAUNCHED for the purchase, so you can record the id and wait for the webhook.
ONRAMP_TRANSACTION_COMPLETE (without the _V2) is the older event. It emits the raw purchase row and is skipped entirely for purchases to a raw external address, so prefer _V2 in new code.
Don’t treat the user reaching your success UI as proof of settlement — a provider can still fail or cancel a purchase after its checkout closes. The webhook (or a FINISHED status from the lookup) is the authoritative signal that the crypto was delivered.

Test mode

A test purchase runs the real quote and the real provider UI but is never charged — the Apple Pay paths simulate the confirmation instead of taking payment. Test mode routes the purchase to the provider’s own sandbox environment, so you’re exercising their test infrastructure rather than a Para-side simulation: MoonPay and Stripe run against their test keys, and Ramp against its demo environment. Destination networks are swapped for their testnet equivalents too (Ethereum → Sepolia, Solana → Devnet), so use a testnet address or wallet when checking delivery. Where technically possible, every provider honors it; current exceptions include the Coinbase authenticated widget, which always runs against the production widget because Coinbase’s sandbox host doesn’t serve that flow. You rarely need to set testMode yourself; it defaults by environment: Test purchases still emit onramp.completed and still appear in the purchase lookup, each flagged with testMode: true. Filter on that field before recording a purchase in production accounting.

Parameters

useInitiateFiatRamp is the low-level entry point behind the Para Modal’s own Buy button, so it works with the same Developer Portal configuration and the same in-modal Buy/Withdraw UI. See initiateOnRampTransaction for the underlying core method.