Guide

Web integration · Stellar / USDC

Core non-custodial Web flow: sessions, iframe SDK, Stellar USDC signing and memos, testing.

Core flowStellarUSDC

MoneyGram Ramps: Web Integration Guide (Stellar / USDC)

Widget + API keys · Stellar / USDC

This guide is for Stellar wallets integrating through the same widget + API keys model as Solana. You do not implement SEP-10 or SEP-24 directly. MoneyGram handles anchor interaction inside the widget and REST API.

Shared credentials and environments: Getting started. Legacy SEP anchor partners: SEP guide.

Also see: Stellar hub · Quickstart · Web · React Native guide · API reference


Overview

MoneyGram Ramps lets Stellar wallet partners embed cash-out (off-ramp) and cash-in (on-ramp) flows for USDC.

Cash-out: The user sends USDC on Stellar to MoneyGram's deposit address (with a memo when required). The widget handles KYC and quoting. Your app signs the transfer when the widget requests it, then the user receives a MoneyGram reference number for cash pickup.

Cash-in: The user selects a MoneyGram location, deposits cash at the counter, and receives USDC in their Stellar wallet. No on-chain signing is required in the standard happy path.

Host: createRamps, a raw iframe, and React Native share one message contract. Use Embed the widget. This guide covers the Stellar non-custodial flow, including Memo.id and the USDC issuer.

Integration surface:

LayerYour responsibility
BackendPOST /v1/sessions with secret key (ramps_sk_*)
FrontendEmbed the MoneyGram widget with public key + session token
WalletSign Stellar USDC transfers when the widget emits RAMPS_SIGN_TRANSACTION

Integration time: 30–60 minutes for a basic sandbox flow
Chain: Stellar
Asset: USDC


SandboxProduction
Public key (pk)ramps_pk_sbox_...ramps_pk_prod_...
Secret key (sk)ramps_sk_sbox_...ramps_sk_prod_...

The secret key must only ever be used server-side.

Environments

Sandbox (playground)Production
API basehttps://playground.xramps.moneygram.com/apihttps://xramps.moneygram.com/api
SessionPOST {base}/v1/sessionsSame pattern
Real moneyNoYes
Session JWT TTL1 hour1 hour

Sandbox and production keys are separate clusters. A playground key returns 401 on production hosts.

Stellar USDC (reference)

Use tokenAddress and requiredNetwork from the sign payload. Use issuer when the payload includes it. Otherwise choose the Circle issuer from requiredNetwork. Do not hardcode the mainnet issuer when testing with sandbox keys.

Testnet (sandbox keys)Mainnet (production keys)
USDC asset codeUSDCUSDC
Typical testnet issuerGBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5
Typical mainnet issuerGA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN

Cash-out (off-ramp)Cash-in (on-ramp)
User actionSend USDC on StellarDeposit cash at a MoneyGram location
Widget modemode=off-rampmode=on-ramp
Partner callbackSign USDC transfer (+ memo if provided)Not used in the standard flow
Completion artifactreferenceNumber for pickupCounter confirmation / reference

Create a backend endpoint that calls MoneyGram's session API. Pass the user's Stellar G-address and chain: "stellar".

TypeScript
async function createMoneyGramSession(req, res) {  const secretKey = process.env.MONEYGRAM_SK // ramps_sk_sbox_...  if (!secretKey) return res.status(500).json({ error: 'Secret key not configured' })   const { walletAddress } = req.body ?? {}   const mgiRes = await fetch('https://playground.xramps.moneygram.com/api/v1/sessions', {    method: 'POST',    headers: {      'Content-Type': 'application/json',      'x-api-key': secretKey,    },    body: JSON.stringify({      walletAddress, // G... address of the end user      chain: 'stellar',    }),  })   const data = await mgiRes.json()  if (!mgiRes.ok) return res.status(mgiRes.status).json(data)   return res.status(200).json({    sessionToken: data.sessionToken,    sessionId: data.sessionId,    widgetUrl: data.widgetUrl,  })}

Call this endpoint fresh each time the user opens the widget. Do not cache session tokens beyond their 1-hour TTL.


Load the widget SDK and initialize with your public key and the session token from Step 1.

HTML
<script src="https://playground.xramps.moneygram.com/sdk/index.global.js"></script>
TypeScript
const { createRamps } = window.RampsSDK const session = await fetch('/api/moneygram-session', {  method: 'POST',  headers: { 'Content-Type': 'application/json' },  body: JSON.stringify({ walletAddress: userGAddress }),}).then((r) => r.json()) const ramps = createRamps({  container: document.getElementById('ramps-widget-container'),  sessionToken: session.sessionToken,  widgetUrl: session.widgetUrl,   wallet: {    address: userGAddress,    chain: 'stellar',    asset: 'USDC',    walletType: 'non-custodial',  },   onSignTransaction: async (tx) => {    // Step 3: sign Stellar USDC transfer    return await signStellarUsdcTransfer(tx)  },   onComplete: (transaction) => {    console.log('Done:', transaction.referenceNumber, transaction.type)  },   onError: (err) => console.error(err),}) ramps.open()

For cash-in, 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.

TypeScript
const widgetUrl = new URL(session.widgetUrl)widgetUrl.searchParams.set('mode', 'on-ramp') const ramps = createRamps({  // ...sessionToken, wallet, transaction: { type: 'on-ramp' }  widgetUrl: widgetUrl.toString(),  mode: 'on-ramp',})

mode: 'on-ramp' on createRamps is sent in RAMPS_CONFIG and sets the flow. Set transaction.type to 'on-ramp' as well.


When the user commits a cash-out, the widget calls onSignTransaction with a payload like:

FieldDescription
chain'stellar'
asset'USDC' (required)
toMoneyGram deposit G-address
amountUSDC amount as a string
memoRequired when present. Numeric settlement memo. Attach with Memo.id
tokenAddressAsset code (USDC)
issuerCircle issuer when present. If absent, choose it from requiredNetwork
requiredNetwork'testnet' or 'mainnet'. Match your wallet network

Example with @stellar/stellar-sdk. Reuse usdcIssuer and settlementMemo anywhere you build a Stellar cash-out payment (Web, React Native, custodial).

TypeScript
import {  Asset,  BASE_FEE,  Horizon,  Keypair,  Memo,  Networks,  Operation,  TransactionBuilder,} from '@stellar/stellar-sdk' const TESTNET_USDC_ISSUER = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5'const MAINNET_USDC_ISSUER = 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN' function usdcIssuer(requiredNetwork: 'mainnet' | 'testnet'): string {  if (requiredNetwork === 'mainnet') return MAINNET_USDC_ISSUER  return TESTNET_USDC_ISSUER} /** Settlement memos are Stellar ID memos (unsigned 64-bit), not text memos. */function settlementMemo(memo: string) {  if (!/^[0-9]+$/.test(memo)) {    throw new Error('Settlement memo must be a numeric Stellar ID memo')  }  return Memo.id(memo)} async function signStellarUsdcTransfer(tx: {  to: string  amount: string  memo?: string  tokenAddress?: string  issuer?: string  requiredNetwork?: string}): Promise<string> {  if (tx.requiredNetwork !== 'mainnet' && tx.requiredNetwork !== 'testnet') {    throw new Error('Sign payload is missing requiredNetwork')  }  const requiredNetwork = tx.requiredNetwork  const networkPassphrase =    requiredNetwork === 'mainnet' ? Networks.PUBLIC : Networks.TESTNET  const horizon =    requiredNetwork === 'mainnet'      ? new Horizon.Server('https://horizon.stellar.org')      : new Horizon.Server('https://horizon-testnet.stellar.org')   const sourceKeypair = Keypair.fromSecret(process.env.USER_SECRET!) // your wallet signing  // tokenAddress is the asset code (USDC). Prefer issuer on the payload.  const usdc = new Asset(    tx.tokenAddress ?? 'USDC',    tx.issuer ?? usdcIssuer(requiredNetwork),  )   const account = await horizon.loadAccount(sourceKeypair.publicKey())   let builder = new TransactionBuilder(account, {    fee: BASE_FEE,    networkPassphrase,  }).addOperation(    Operation.payment({      destination: tx.to,      asset: usdc,      amount: tx.amount,    })  )   if (tx.memo?.trim()) {    builder = builder.addMemo(settlementMemo(tx.memo.trim()))  }   const transaction = builder.setTimeout(180).build()  transaction.sign(sourceKeypair)   const result = await horizon.submitTransaction(transaction)  return result.hash}

Memo: When tx.memo is set, attach it as a Stellar ID memo (Memo.id). The value is a numeric string (an unsigned 64-bit integer). Memo.text does not match MoneyGram's deposit record, and check-deposit rejects it. Omitting a memo that is present also fails deposit verification.

Issuer: tokenAddress is the asset code (USDC). Use issuer when the sign payload includes it. If it is absent, choose the Circle issuer from requiredNetwork (table above). Do not send the testnet issuer on a mainnet payload. Sandbox keys use testnet. Production keys use mainnet. Widgets deployed before issuer was added omit the field.

iframe / WebView postMessage alternative

If you embed widgetUrl in your own iframe, or in a React Native WebView, use Embed the widget. The payload on RAMPS_SIGN_TRANSACTION matches onSignTransaction. Reply with RAMPS_SIGN_SUCCESS and { txHash, walletAddress }, or RAMPS_SIGN_ERROR.


If you hold customer USDC in an omnibus wallet:

  • Register as custodial at onboarding.
  • Pass customerIdentifier (stable internal user ID) in POST /v1/sessions.
  • The widget may emit RAMPS_DEPOSIT_ADDRESS instead of RAMPS_SIGN_TRANSACTION so your backend moves funds from the hot wallet.

See the Web custodial cash-out guide for the same session and callback patterns on Stellar.


  1. Complete KYB in the partner portal.
  2. Submit verified Playground transaction IDs for integration tests.
  3. Configure product fees.
  4. Request production activation for live keys (ramps_pk_prod_ / ramps_sk_prod_).

Production keys require completed KYB and MoneyGram agent ID provisioning.


REST API reference

The widget uses the same REST API documented in the OpenAPI spec:

Key paths: /v1/sessions, /v1/transactions, /v1/transactions/{id}/status, /v1/transactions/{id}/check-deposit (cash-out, with Stellar txHash).


This guide (widget + API keys)Legacy SEP-24 path
Credentialsramps_pk_* / ramps_sk_*Whitelist + anchor JWT
IntegrationWidget + session + sign callbackSEP-10 + SEP-24 + polling
OnboardingSelf-serve sandbox keys (when enabled)Manual whitelist review

Existing SEP-24 partners may continue on the anchor path during transition. New Stellar pilot partners should use this widget path.


Support

For pilot access issues, contact your MoneyGram Ramps integration contact. Do not paste secret keys or customer PII into support channels.