Guide

Custodial integration overview

Shared foundation for custodial cash-out and cash-in: onboarding, omnibus wallets, sessions, profiles, notifications, and security.

CustodialShared setupSolanaUSDC

Custodial integration overview (Solana and Stellar · USDC)

Shared foundation for custodial partners · Cash-out and cash-in · August 2026

This is the common ground for every custodial integration: onboarding, omnibus wallets, sessions, profiles, and the security rules that apply to both flows. Read it once, then follow the guide for the flow you are building.

The flow guides do not repeat this material. They link back here.


Who this is for

You are a custodial partner: you hold customer USDC in a shared omnibus or hot wallet, and your end users never sign a blockchain transaction. Customers are identified by a customerIdentifier you supply rather than by a wallet address.

If your end users sign with their own keys, you are non-custodial. Use the guide for your chain: Web · Solana, React Native · Solana, Web · Stellar, or React Native · Stellar.

Your custody model (walletCustody) is set at partner onboarding and stored on your partner record. You cannot switch per session: the API derives walletType from your registered configuration.


Everything on this page applies to both. Everything in this table differs, which is why the step-by-step guides stay separate.

DimensionCustodial cash-out (off-ramp)Custodial cash-in (on-ramp)
Widget modeoff-rampon-ramp
Omnibus wallet roleSource (custodianCashOutWallet)Destination (custodianCashInWallet)
Partner on-chain actionRequired. Transfer USDC after MoneyGram allocates a deposit addressNone
Key widget callbackonDepositAddress (web) / RAMPS_DEPOSIT_ADDRESS (React Native)None. Do not implement onDepositAddress
What the customer doesCollects cash at an agent locationPays cash at a store using a confirmation code
Your ledger actionDebit the internal balance, then send from the hot walletCredit the internal balance once the transaction is completed
Terminal signal on the done screenReference numberConfirmation code, which is not your signal to credit

Cash-out is transfer orchestration after MoneyGram allocates a deposit address. Cash-in is persist, wait, then credit on confirmed settlement.


Confirm all of the following with MoneyGram before you integrate.

Custody mode

Your partner account must be registered with walletCustody: 'custodial'. This is selected during partner registration or set by MoneyGram ops.

Omnibus wallets

FieldRoleNeeded for
custodianCashOutWalletSource wallet that sends USDC to MoneyGramCash-out
custodianCashInWalletDestination wallet that receives USDC from MoneyGramCash-in

Set these in the partner portal (Settings → Profile), or ask MoneyGram ops to configure them on your partner record. Sandbox may use one address for both. On cash-in, the API overrides any destinationWalletAddress you send with your configured custodianCashInWallet.

API keys

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

The secret key is server-side only, for session creation. Never ship it in client-side JavaScript or a mobile bundle.

Allowed origins

Allowlist your web app origins in the partner portal before the keys work in the widget.

KYB and funding wallets

Complete KYB (Live Hub) before production. Custodial partners usually provide funding wallet addresses during KYB so MoneyGram can verify the omnibus setup. An approved KYB is also required before you can set a production webhook URL.


2. Environments

SandboxProduction
API basehttps://playground.xramps.moneygram.com/apihttps://xramps.moneygram.com/api
Session endpointPOST /v1/sessionsSame path
Widget URLReturned by the session API (widget.html)Returned by the session API
Solana USDC mint4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU (devnet, SPL, not Token-2022)EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v (mainnet SPL)
Stellar USDC issuerGBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 (testnet)GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN (mainnet)
Stellar settlement memoMemo.id (numeric string) when depositMemo is presentSame

Always use the widgetUrl from the session response. It carries the correct mode for the flow, so do not hardcode it. Do not use stub-widget.html.

Solana omnibus addresses are base58 public keys. Stellar omnibus addresses are G-addresses. A Stellar cash-in destination needs a USDC trustline and enough XLM to hold the trustline. Cash-out payments on Stellar must include depositMemo as Memo.id when it is present.


3. Sessions

Custodial sessions require a customerIdentifier: a stable opaque string (UUID, hashed email, internal user ID) that identifies the end customer across transactions. Maximum 255 characters.

The identifier is embedded in the session JWT server-side. The widget cannot forge or change it.

TypeScript
const chain = 'solana' // or 'stellar' 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({    customerIdentifier,          // from your authenticated session, never from the client body alone    walletAddress: omnibusWallet, // cash-out: custodianCashOutWallet. cash-in: custodianCashInWallet    chain, // 'solana' | 'stellar'    // walletTransactionId: '550e8400-e29b-41d4-a716-446655440000', // optional, your own correlation id  }),})

Request fields

FieldRequiredDescription
customerIdentifierYesStable internal user ID, max 255 characters
walletAddressRecommendedThe omnibus wallet for the flow you are starting
chainYes for widget sessions'solana' or 'stellar'
walletTransactionIdOptionalUUID, your reference for MoneyGram correlation (custodial only)

Response

JSON
{  "sessionId": "b4641a16-2b96-4afe-acb6-b71682305d08",  "sessionToken": "eyJhbGci...",  "walletType": "custodial",  "language": "en",  "widgetUrl": "https://playground.xramps.moneygram.com/widget.html?mode=off-ramp"}

Common errors

StatusCause
400Missing customerIdentifier on a custodial partner
403Public key used where the secret key is required
401Invalid or revoked API key

Session TTL: tokens expire after 1 hour. Create a fresh session each time the user opens the widget, and do not cache tokens.


Fetch your registered configuration at runtime rather than hardcoding wallet addresses in your frontend.

TEXT
GET /v1/partners/configAuthorization: Bearer <sessionToken>
JSON
{  "walletType": "custodial",  "custodianCashOutWallet": "YourSolanaHotWallet...",  "custodianCashInWallet": "YourSolanaCashInWallet...",  "depositWallets": { }}

Use the field that matches your flow as the wallet.address you pass to the widget.


5. Profile APIs

Because custodial customers share an omnibus wallet, profile lookup uses customerIdentifier instead of a blockchain address. Both endpoints require a Bearer session token, not a raw API key.

EndpointPurpose
POST /v1/profilesPre-create or find a profile server-side. Uses the customerIdentifier from the session JWT. Returns { profileId, isNewProfile, ... }
GET /v1/profiles/meCalled by the widget on load when walletType === 'custodial'. 200 prefills the KYC form, 204 means a first-time customer who completes KYC in the widget

Three notification paths cover both flows, and they are documented once in Transaction status and webhooks:

PathUse it for
Widget eventsUser-facing UX in the session that is open right now
GET /v1/transactions/{id}/status pollingAuthoritative state, on demand
Partner webhooksServer-side push after the user has closed the widget

Two rules apply to every custodial integration:

  • Persist mgiTransactionId when the transaction is created. It is your correlation key for webhooks. See RAMPS_TRANSACTION_CREATED.
  • Confirm with GET /status?sync=true before moving money. Webhook status values use a different vocabulary from the Ramps API status values, and a widget event is a UX signal rather than settlement.

These apply to both flows and are not optional in production.

  • Never trust browser-supplied transfer parameters. Take the deposit address and the amount from GET /status on your server, not from the widget callback payload.
  • Make funds movement idempotent. Key a durable record on the Ramps transaction id and return the stored result on retry. Widget callbacks and webhooks can both repeat.
  • Debit atomically and reverse on failure. Reserve funds only if the balance is sufficient, in one operation, and compensate if the on-chain send fails afterwards.
  • Verify webhook signatures before acting on a notification, using the scheme in Transaction status and webhooks.
  • Keep the secret key server-side. Sessions are created from your backend, and the widget only ever sees a session token.
  • Derive customerIdentifier from your authenticated session. A client-supplied user ID lets one customer read another customer's KYC prefill.

Partners often keep a non-custodial production integration while piloting custodial on a second partner record. That works, with three things to know.

  • Each partner record has its own API keys. A custodial key and a non-custodial key are not interchangeable, and walletType follows the record rather than the request.
  • Each record has its own webhook URL setting in the partner portal. Both records may point at the same backend endpoint. Events carry mgiTransactionId, so route on the stored transaction rather than on which record delivered the event.
  • Both records on one environment verify webhooks against the same public key. Sandbox and production keys differ from each other, and both are published in Transaction status and webhooks.

MoneyGram provisions each record with its own agent and main office IDs. If you believe your two records share one, raise it with your integration contact before configuring webhooks on both.


Where to go next

You are buildingStart here
Custodial cash-out on the web (Solana)Quickstart, then the Web guide
Custodial cash-out in React Native (Solana)Quickstart, then the React Native guide
Custodial cash-out on the web (Stellar)Web guide
Custodial cash-out in React Native (Stellar)React Native guide
Custodial cash-in (both chains)Quickstart, then the cash-in guide
Embed (Web SDK, iframe, React Native)Embed the widget
Status, polling, and webhooksTransaction status and webhooks
Widget configuration and callbacksAPI reference