Guide
Embed the widget
One host contract for Web createRamps, a raw iframe, and React Native WebView. Chain signing stays in the flow guides.
Embed the widget
One host page for every chain, custody model, and flow. Solana and Stellar signing stay in the flow guides.
Sessions, KYC, quotes, cash-in rules, cash-out rules, and webhooks do not change between Web and React Native. Only the host changes.
| Host | When to use it |
|---|---|
| Web createRamps | Browser app using the iframe SDK |
| Web iframe | Browser app that loads widgetUrl in its own iframe |
| React Native WebView | Mobile app using react-native-webview |
The message contract below is the same for the iframe and the WebView. createRamps wraps that contract. Do not keep a second copy in a platform guide.
Chain signing (Solana mint and ATA, Stellar issuer and Memo.id) stays in the flow guide:
| Chain | Non-custodial | Custodial cash-out |
|---|---|---|
| Solana | Web · React Native | Web · React Native |
| Stellar | Web · React Native | Web · React Native |
Use the widgetUrl from POST /v1/sessions. It is widget.html with a mode query. Do not use stub-widget.html.
Widget and host exchange { type, payload }. For cash-out and cash-in the order is:
RAMPS_READY → RAMPS_CONFIG → RAMPS_CHECK_BALANCE → RAMPS_BALANCE_RESULT → RAMPS_TRANSACTION_CREATED after validate, before commit → sign or deposit (cash-out only) → RAMPS_TRANSACTION_COMPLETE → RAMPS_CLOSE
| Message | Direction | When | Payload |
|---|---|---|---|
RAMPS_READY | Widget → host | Widget loaded | none |
RAMPS_CONFIG | Host → widget | Reply to RAMPS_READY | Session, wallet, optional pre-fill. See below |
RAMPS_CHECK_BALANCE | Widget → host | Before quote | { amount, asset: 'USDC' } |
RAMPS_BALANCE_RESULT | Host → widget | Reply | { balance, sufficient, asset: 'USDC' } |
RAMPS_TRANSACTION_CREATED | Widget → host | After validate succeeds, before commit | { id, type, status: 'created', ... } |
RAMPS_SIGN_TRANSACTION | Widget → host | Non-custodial cash-out | Same shape as onSignTransaction |
RAMPS_DEPOSIT_ADDRESS | Widget → host | Custodial cash-out | { address, amount, chain, asset, memo? } |
RAMPS_SIGN_SUCCESS | Host → widget | Transfer submitted | { txHash, walletAddress } |
RAMPS_SIGN_ERROR | Either direction | Signing or transfer failed | { error } |
RAMPS_TRANSACTION_COMPLETE | Widget → host | Done screen | Reference number (cash-out) or confirmation code (cash-in) |
RAMPS_TRANSACTION_FAILED | Widget → host | Flow failed | { transactionId, reason } |
RAMPS_CLOSE | Widget → host | User closed the widget | none |
RAMPS_TRANSACTION_CREATED.type is 'cash-out' or 'cash-in'. Widget config transaction.type is 'off-ramp' or 'on-ramp' for the same flows. Persist id, and mgiTransactionId when it is present. Full field list: onTransactionCreated.
Cash-in does not send RAMPS_SIGN_TRANSACTION or RAMPS_DEPOSIT_ADDRESS. The confirmation code on RAMPS_TRANSACTION_COMPLETE is not settlement. Credit custodial cash-in only after GET /status?sync=true returns completed.
Custodial cash-out sends RAMPS_DEPOSIT_ADDRESS instead of RAMPS_SIGN_TRANSACTION. Reply with RAMPS_SIGN_SUCCESS and the on-chain txHash after your server sends the funds. Read the deposit address and amount from GET /v1/transactions/:id/status, not from the browser payload alone.
RAMPS_CONFIG
{ sessionToken: string wallet: { address: string // Solana public key or Stellar G-address chain: 'solana' | 'stellar' asset: 'USDC' walletType: 'custodial' | 'non-custodial' } transaction?: { type: 'off-ramp' | 'on-ramp' asset: 'USDC' amount?: number destinationCountry?: string } customer?: { /* KYC pre-fill. See the flow guide. */ } devConfig?: { mockMode: false, apiBaseUrl: string }}
For cash-in, set transaction.type to 'on-ramp'. The session widgetUrl already includes mode=off-ramp. Replace that value. Do not append a second mode. The widget reads the first mode parameter and would stay on cash-out.
const widgetUrl = new URL(session.widgetUrl)widgetUrl.searchParams.set('mode', 'on-ramp')
With createRamps, also pass mode: 'on-ramp'. That value is sent in RAMPS_CONFIG and sets the flow.
To reopen a transaction, send RAMPS_CONFIG with mode: 'view' and transactionId set to the id from RAMPS_TRANSACTION_COMPLETE. With createRamps, pass viewTransaction: { id } instead.
Load the SDK from the widget origin. It is not an npm package.
<script src="https://playground.xramps.moneygram.com/sdk/index.global.js"></script>const { createRamps } = window.RampsSDK const ramps = createRamps({ container: document.getElementById('ramps-widget'), sessionToken: session.sessionToken, widgetUrl: session.widgetUrl, wallet: { address: walletAddress, chain, // 'solana' | 'stellar' asset: 'USDC', walletType, // 'custodial' | 'non-custodial' }, transaction: { type: 'off-ramp' }, // 'on-ramp' for cash-in onTransactionCreated: (tx) => { // Persist tx.id. Persist tx.mgiTransactionId when present. }, onSignTransaction: async (tx) => { // Non-custodial cash-out only. Sign with the chain guide sample. Return txHash. return txHash }, onDepositAddress: async () => { // Custodial cash-out only. Do not implement this for cash-in. return txHash }, onComplete: (tx) => { // Cash-out: reference number. Cash-in: confirmation code, not settlement. },}) ramps.open()
Callbacks map onto the messages above. onTransactionCreated is RAMPS_TRANSACTION_CREATED. onSignTransaction is RAMPS_SIGN_TRANSACTION. onComplete is RAMPS_TRANSACTION_COMPLETE.
Load session.widgetUrl in an iframe. Listen for message events from the widget origin, and post replies to iframe.contentWindow.
const widgetOrigin = new URL(session.widgetUrl).origin window.addEventListener('message', (event) => { if (event.origin !== widgetOrigin) return const { type, payload } = event.data ?? {} if (type === 'RAMPS_READY') { iframe.contentWindow?.postMessage({ type: 'RAMPS_CONFIG', payload: { sessionToken: session.sessionToken, wallet: { address: walletAddress, chain, asset: 'USDC', walletType }, transaction: { type: 'off-ramp' }, }, }, widgetOrigin) } if (type === 'RAMPS_TRANSACTION_CREATED') { // Persist payload.id and payload.mgiTransactionId when present. } if (type === 'RAMPS_SIGN_TRANSACTION') { // Non-custodial cash-out. Payload matches onSignTransaction. // Reply with RAMPS_SIGN_SUCCESS { txHash, walletAddress } or RAMPS_SIGN_ERROR. } if (type === 'RAMPS_DEPOSIT_ADDRESS') { // Custodial cash-out only. }})
Check event.origin before acting. The payload fields for RAMPS_SIGN_TRANSACTION match onSignTransaction in the API reference.
The contract is the iframe contract. The transport is react-native-webview.
- Receive widget messages on
onMessage. - Send host messages with
injectJavaScript. The injected script must end withtrue;. Otherwise it is ignored. - Do not put the secret API key in the app binary. Create the session on your server.
- Production WebViews load HTTPS. Set Android
usesCleartextTrafficto false in production. iOS needs no ATS exception for the MoneyGram widget host. - Reject messages whose page URL is not the widget origin.
function post(type: string, payload?: Record<string, unknown>) { const json = JSON.stringify(payload ? { type, payload } : { type }) const encoded = btoa(unescape(encodeURIComponent(json))) webViewRef.current?.injectJavaScript(` (function () { var data = JSON.parse(decodeURIComponent(escape(atob('${encoded}')))); window.dispatchEvent(new MessageEvent('message', { data: data })); })(); true; `)}
function onMessage(event: WebViewMessageEvent) { if (!event.nativeEvent.url.startsWith(widgetOrigin)) return const { type, payload } = JSON.parse(event.nativeEvent.data) if (type === 'RAMPS_READY') post('RAMPS_CONFIG', config) if (type === 'RAMPS_TRANSACTION_CREATED') persist(payload) if (type === 'RAMPS_SIGN_TRANSACTION') signAndReply(payload)}
Set javaScriptEnabled and domStorageEnabled. Set cacheEnabled={false} so a cached widget does not run against a new session.
Worked component examples stay in the React Native flow guides. If those examples and this table disagree, this table is the contract.