Guide
Legacy Stellar integration · SEP-10 / SEP-24
Whitelist anchor path: SEP-10 authentication, SEP-24 interactive cash-in and cash-out, on-chain USDC with memo, and MoneyGram reference numbers. New partners: use the Stellar widget hub instead.
MoneyGram Ramps — Stellar Integration Guide (USDC)
Version 1.2 · Stellar + USDC · July 2026
Legacy integration path. This guide is for partners on the Stellar whitelist + SEP-10 / SEP-24 anchor model. New Stellar wallet integrations should use the widget + API keys path: Stellar hub, Credentials & environments, and the Web or React Native Stellar quickstarts.
What is MoneyGram Ramps?
MoneyGram Ramps on Stellar lets wallets and exchanges offer USDC cash-in (on-ramp) and cash-out (off-ramp) at MoneyGram locations worldwide. MoneyGram handles KYC, compliance, and settlement. Your wallet integrates via the Stellar anchor protocols SEP-10 (authentication) and SEP-24 (interactive deposit and withdrawal).
Supported asset: USDC on Stellar.
Integration time: 1–3 days for a Stellar-native wallet
Coverage: 480,000+ MoneyGram locations · 170+ countries for off-ramps
On and Off Ramps
Cash-out (off-ramp): the user sends USDC to MoneyGram and picks up cash at an MoneyGram agent location.
Cash-in (on-ramp): the user pays cash at a MoneyGram agent and receives USDC in their Stellar account.
Cash-out (withdrawal)
- Authenticate the user via SEP-10 (Stellar Web Auth).
- Initiate a withdrawal using the SEP-24 interactive endpoint.
- Launch MoneyGram's UI in a webview.
- User completes the UI flow by providing transaction details and KYC.
- Poll transaction status until
pending_user_transfer_start. - Send USDC on Stellar with the memo provided by the anchor.
- Poll again until
pending_user_transfer_complete, then display MoneyGram's reference number (external_transaction_id) for agent pickup.
Cash-in (deposit)
- Authenticate the user via SEP-10 (Stellar Web Auth).
- Initiate a deposit using the SEP-24 interactive endpoint.
- Launch MoneyGram's UI in a webview.
- User completes the UI flow by selecting a deposit location and providing KYC details.
- Poll transaction status until
pending_user_transfer_start— the user pays cash at the selected agent. - Receive USDC in the user's Stellar account after the agent confirms payment.
Step 1 — Get allowlisted (Sandbox/Stellar testnet)
Note: The information below is for sandbox/testnet access only. Only provide your production domain/addresses during Certification and go-live in Production.
MoneyGram must allowlist your wallet before you can connect to the Stellar anchor. To provide the necessary info for allowlisting, log into the MoneyGram Ramps partner portal and go to the Developers section (you will need to create an account if you do not already have one.)
You will need to provide the following under "Whitelist in TestNet":
Non-custodial wallets
- Domain where your
stellar.tomlis hosted (must include a signing key for Stellar testnet) - Wallet name and intended use case
Custodial wallets
- Wallet domain used in auth (e.g.
wallet-provider.com) - Authentication wallet address (testnet)
- Withdraw wallet address (testnet)
- Deposit wallet address (testnet)
Tip: Verify your TOML file with the Stellar TOML Checker before requesting access.
Step 2 — Install the Stellar SDK
- Install the Stellar Wallet SDK:
yarn add @stellar/typescript-wallet-sdk
Step 3 — Implement SEP-10, SEP-24, and Status page
Step 4 — Test and Certify the integration in sandbox
Provide the Ramps team with transaction IDs and/or reference numbers for your test transactions — see Certification and go-live in Production.
Step 5 — Complete KYB and legal agreements
Step 6 — Provide your production domain/keys and go-live
See Production domain and keys.
SEP-1 (stellar.toml)
MoneyGram publishes its current Signing Key, SEP-10, and SEP-24 endpoints in its stellar.toml. Treat this as the source of truth rather than hardcoding values.
| Environment | stellar.toml |
|---|---|
| Sandbox | https://extmgxanchor.moneygram.com/.well-known/stellar.toml |
| Production | https://mgxanchor.moneygram.com/.well-known/stellar.toml |
SEP-10 authentication
Authenticate users with Stellar Web Auth before any SEP-24 call.
Anchor endpoints
| Environment | Home domain | Auth endpoint |
|---|---|---|
| Sandbox | extmgxanchor.moneygram.com | https://extmgxanchor.moneygram.com/stellarsepservice/auth |
| Production | mgxanchor.moneygram.com | https://mgxanchor.moneygram.com/stellarsepservice/auth |
MoneyGram signing keys (verify challenge signatures)
| Network | Public key |
|---|---|
| Sandbox | GCUZ6YLL5RQBTYLTTQLPCM73C5XAIUGK2TIMWQH7HPSGWVS2KJ2F3CHS |
| Production | GD5NUMEX7LYHXGXCAD4PGW7JDMOUY2DKRGY5XZHJS5IONVHDKCJYGVCL |
Authentication flow
- Request an authentication challenge for the user's Stellar account.
- MoneyGram returns a challenge transaction.
- Verify MoneyGram's signature on the challenge.
- Sign the challenge with your wallet's signing key.
- Submit the signed challenge and receive a JWT (
authToken).
import { Wallet, SigningKeypair } from '@stellar/typescript-wallet-sdk' const MGI_HOME = 'extmgxanchor.moneygram.com' // sandbox// const CLIENT_DOMAIN = 'mywalletdomain.com' // required for non-custodial wallets async function authenticate(authSecretKey: string) { const wallet = Wallet.TestNet() const anchor = wallet.anchor({ homeDomain: MGI_HOME }) const sep10 = await anchor.sep10() const authKey = SigningKeypair.fromSecret(authSecretKey) const authToken = await sep10.authenticate({ accountKp: authKey }) // const authToken = await sep10.authenticate({ accountKp: authKey, clientDomain: CLIENT_DOMAIN }) // required for non-custodial wallets return authToken}
Custodial vs non-custodial
| Custodial | Non-custodial | |
|---|---|---|
| Accounts | Common Stellar accounts for auth and funds | Individual user accounts |
| Home domain in SEP-10 | Not required | Required — pass your wallet domain |
| Client domain in SEP-10 | Not required | Required — provide the client domain you allowlisted with MoneyGram |
| User identification | Positive integer user ID as memo (≤ 64 bits) | User's public key + your TOML SIGNING_KEY |
| SEP-9 "amount" field | Required on SEP-24 | Optional |
Learn more: SEP-10 specification
SEP-24 interactive flow
After SEP-10, call SEP-24 to start a cash-in or cash-out. The response includes an interactive url and transaction id. Open url in a webview or in-app browser. Use the Stellar Wallet SDK (shown below) rather than calling the endpoint directly.
SEP-24 endpoint
For reference, the underlying SEP-24 endpoint the SDK calls:
| Environment | Endpoint |
|---|---|
| Sandbox | https://extmgxanchor.moneygram.com/stellarsepservice/sep24 |
| Production | https://mgxanchor.moneygram.com/stellarsepservice/sep24 |
Cash-out (withdrawal)
User sends USDC · picks up cash at MoneyGram.
const { url, id } = await anchor.sep24().withdraw({ authToken, withdrawalAccount: FUNDS_STELLAR_KEYPAIR.public_key, assetCode: 'USDC', lang: 'en', extraFields: { // Required for custodial wallets; optional for non-custodial amount: '50.00', },})
Cash-in (deposit)
User pays cash at MoneyGram · receives USDC in their Stellar account.
const { url, id } = await anchor.sep24().deposit({ authToken, destinationAccount: FUNDS_STELLAR_KEYPAIR.public_key, assetCode: 'USDC', lang: 'en', extraFields: { amount: '100.00', // required for custodial; optional otherwise },})
Response includes url (open in webview) and id (save for polling).
Learn more: SEP-24 specification
Passing customer info (SEP-9)
MoneyGram supports selected SEP-9 fields. Include them in your SEP-24 interactive request body "extraFields" attribute to pre-fill KYC and reduce friction in MoneyGram's UI.
Custodial wallets must pass amount. Non-custodial wallets may omit it.
{ ... "extraFields": { "asset_code": "USDC", "amount": "15", "account": "GDFKIZYZ2HHOXWLSHMVJRQFKVUJTQJAPSHR2HWUB7BLNNZ4BQPQBM36T", "first_name": "Jhonny", "last_name": "Adrian", "birth_date": "1999-12-31", "mobile_number": "+15076451253", "address": "2119 Hidden Valley Rd", "city": "Northfield", "postal_code": "55057", "address_country_code": "USA", "state_or_province": "US-MN" }}
| Field | Format |
|---|---|
first_name | Given / first name |
last_name | Family name. Second last name allowed, comma-separated: "Garcia, Lopez" |
mobile_number | E.164 with country code, e.g. +15076451253 |
birth_date | YYYY-MM-DD |
address | Street line 1; line 2 comma-separated if needed |
city | City or town |
postal_code | Postal / ZIP code |
address_country_code | 3-letter ISO, e.g. USA, CAN |
state_or_province | ISO-3166-2, e.g. US-MN. Used for USA, CAN, MEX only |
Listen for close notification (postMessage)
MoneyGram's interactive UI notifies your host app via postMessage so you can close the webview and resume polling in your wallet.
Current Ramps UI — COMMIT_RESULT
When the user successfully commits in MoneyGram's UI, the host receives:
| Field | Value |
|---|---|
type | "COMMIT_RESULT" |
payload.transaction | SEP-24-style transaction object (id, status, etc.) |
timestamp | Milliseconds since epoch |
Web iframe
window.addEventListener('message', (event) => { const message = event.data if (message?.type === 'COMMIT_RESULT') { const transaction = message.payload.transaction // Close webview, start/refresh polling with transaction.id }})
React Native WebView
<WebView onMessage={(event) => { const message = JSON.parse(event.nativeEvent.data) if (message.type === 'COMMIT_RESULT') { const transaction = message.payload.transaction // Close modal, poll GET /sep24/transaction?id=transaction.id } }}/>
iOS WKWebView: register a script message handler named rampsUI; parse the JSON string body and check type === 'COMMIT_RESULT'.
Ignore other message types (e.g. COMMIT_REQUEST) unless you handle the wallet send step separately.
Legacy Stellar UI
Older integrations on stellar.moneygram.com may post a transaction object directly. Close the webview when transaction.status === 'pending_user_transfer_start':
window.addEventListener('message', (event) => { const txJson = event.data?.transaction if (txJson?.status === 'pending_user_transfer_start') { webview.close() }})
Poll transaction status
Poll GET /sep24/transaction?id=... (or use the Wallet SDK watcher) until status reaches pending_user_transfer_start, then perform the on-chain step for cash-out or wait for inbound USDC on cash-in.
Cash-out: funds must be sent within 30 minutes of reaching pending_user_transfer_start, or the user will be unable to complete their transaction.
Wallet SDK watcher (recommended)
const watcher = anchor.sep24().watcher() const { stop } = watcher.watchOneTransaction({ authToken, assetCode: 'USDC', id: transactionId, onMessage: (transaction) => { if (transaction.status === 'pending_user_transfer_start') { // Cash-out: send USDC (see Send or Receive USDC below). Cash-in: monitor for inbound payment. } if (transaction.status === 'pending_user_transfer_complete') { // Cash-out: show reference number (see Send or Receive USDC below) } }, onSuccess: (transaction) => { // completed / refunded / expired }, onError: (transaction) => { // error / no_market / too_small / too_large },})
Raw endpoint
curl "{MONEYGRAM_DOMAIN}/sep24/transaction?id=$TRANSACTION_ID" \ -H "Authorization: Bearer $SEP10_JWT"
| Environment | {MONEYGRAM_DOMAIN} |
|---|---|
| Sandbox | https://extmgxanchor.moneygram.com |
| Production | https://mgxanchor.moneygram.com |
Status lifecycle
| Status | Meaning | Your action |
|---|---|---|
incomplete | User still in MoneyGram UI | Keep polling |
pending_user_transfer_start | Ready for on-chain step (cash-out: send USDC; cash-in: wait for inbound payment after agent takes cash) | Send USDC (withdrawal) or monitor account (deposit) |
pending_user_transfer_complete | On-chain transfer confirmed · reference number available (withdrawals) | Display external_transaction_id for cash pickup |
pending_anchor | Anchor processing | Keep polling |
completed | Success | Show confirmation |
refunded / expired | Terminal — no further user action | Show appropriate message |
error / no_market / too_small / too_large | Failed or rejected | Inspect message fields; do not retry blindly |
Lookup by other IDs
After on-chain activity you can also fetch by Stellar hash or reference number:
// By anchor transaction id (always available)await anchor.sep24().getTransactionBy({ authToken, id: transactionId }) // By Stellar payment hash (after on-chain transfer)await anchor.sep24().getTransactionBy({ authToken, stellarTransactionId }) // By MoneyGram reference number (external_transaction_id)await anchor.sep24().getTransactionBy({ authToken, externalTransactionId })
Response fields to watch
| Field | When present | Use |
|---|---|---|
withdraw_anchor_account | Cash-out at pending_user_transfer_start | Destination for USDC payment |
withdraw_memo / withdraw_memo_type | Cash-out at pending_user_transfer_start | Memo on outbound payment (id memo type is common) |
amount_in | Cash-out | USDC amount to send |
deposit_memo / deposit_memo_type | Cash-in | Match inbound USDC from MoneyGram |
stellar_transaction_id | After on-chain transfer | Link to Horizon |
external_transaction_id | At pending_user_transfer_complete (withdrawals) | Reference number for agent pickup |
more_info_url | At pending_user_transfer_complete | Hosted page with pickup details |
Send or Receive USDC
Act when status is pending_user_transfer_start.
Cash-out — send USDC to MoneyGram
Use fields from the transaction object:
- Destination:
withdraw_anchor_account - Amount:
amount_in - Memo:
withdraw_memowithwithdraw_memo_type(typicallyid)
import { Horizon } from '@stellar/stellar-sdk' onMessage: async (transaction) => { if (transaction.status !== 'pending_user_transfer_start') return const txBuilder = await stellar.transaction({ sourceAddress: FUNDS_STELLAR_KEYPAIR, baseFee: 10000, timebounds: 180, }) const transferTransaction = txBuilder .transferWithdrawalTransaction(transaction, asset) .build() transferTransaction.sign(FUNDS_STELLAR_KEYPAIR) try { const response = await stellar.submitTransaction(transferTransaction) console.log('Stellar transaction ID:', response.id) } catch (error) { // Handle 504 retries, tx_bad_seq (reload account sequence), etc. // https://developers.stellar.org/docs/learn/encyclopedia/errors-and-debugging/error-handling }}
After submission, continue polling until status reaches pending_user_transfer_complete — the reference number is now available in:
external_transaction_id— show this in your wallet UI for agent pickupmore_info_url— hosted page with pickup instructions
onMessage: (transaction) => { if (transaction.status === 'pending_user_transfer_complete') { console.log( `Reference number: ${transaction.external_transaction_id}`, `Details: ${transaction.more_info_url}`, ) }}
Share the reference number with pickup instructions. Continue polling until completed.
Cash-in — receive USDC from MoneyGram
For deposits, you do not send USDC. The user pays cash at the selected MoneyGram agent. After the agent receives payment, MoneyGram sends USDC to the user's Stellar account.
Monitor the user's account for an inbound USDC payment matching deposit_memo / deposit_memo_type:
import { Horizon } from '@stellar/stellar-sdk' const server = new Horizon.Server('https://horizon-testnet.stellar.org') server .payments() .forAccount(userAccount) .join('transactions') .cursor(cursor) .stream({ onmessage: (payment) => { if ( payment.type !== 'payment' || payment.from === userAccount || payment.asset_code !== 'USDC' ) return const tx = getTransactionByMemo( payment.transaction_attr.memo, payment.transaction_attr.memo_type, ) // your DB lookup if (tx) { console.log('Deposit matched:', payment.transaction_attr.id) } }, })
Trustline: The user's Stellar account must have a USDC trustline before receiving USDC. Learn about trustlines.
No reference number is shown in MoneyGram's UI when the flow starts. The user:
- Selects a deposit location in MoneyGram's UI.
- Commits the transaction and receives a confirmation code to present at the counter (before paying cash).
- Pays cash at the agent.
- After the agent receives payment, MoneyGram may populate a reference linked to the transaction — poll for updated status.
Do not expect external_transaction_id at commit time for cash-in. Continue polling after the user visits the agent.
more_info_url / Refunds
The transaction status lookup returns a more_info_url field — a link to a standalone, MoneyGram-hosted transaction page. Render this page in a webview within your app, the same way you handle the SEP-24 interactive flow URL.
The more_info_url page shows the user their transaction details, and — for cash-out (withdrawal) — includes a Cancel transfer button that lets the user refund their withdrawal before picking up funds.
Before you launch in Production you will need to complete KYB and legal, as well as complete test transactions in the sandbox environment.
Certify in the sandbox environment
Test the following flows in the sandbox environment:
- Cash-out (withdraw) flow
- Cash-out refund (withdraw) — use the Cancel transfer button on the
more_info_urlpage - Cash-in (deposit) — stage a transaction for deposit
After completing your test transactions, provide the transaction ID in the Developer section of the partner portal under Playground test transactions.
KYB and legal agreements
Complete KYB and Legal Agreements in the partner portal. Reach out to the MoneyGram Ramps team with any questions.
Production domain and keys
Once you have certified in the sandbox environment and have completed the KYB/legal agreements, you can provide the Ramps team with your production domain and keys (info is based on your wallet type, see Get allowlisted section above) in the Developers section of the partner portal.
Sandbox Cash-in Location Selection
Cash-in (deposit) requires the user to pre-select the deposit location where they will provide the funds. In the sandbox environment, not all locations that appear in the UI locator are configured for MoneyGram Ramps. The widget highlights approved test locations at the top of the picker in playground and other non-production environments. When testing Cash-in in the sandbox environment, use a location from the following table:
| Country | Address | Location Name |
|---|---|---|
| USA | 2600 RICE CREEK RD, NEW BRIGHTON, MN 55112-5344 | CUB FOODS NEW BRIGHTON |
| USA | 1655 NEW YORK AVE, ARLINGTON, TX 76010-4766 | ELRODS COST PLUS - #7 - ARLINGTON |
| Canada | 407 LAURIER AVE W, OTTAWA, ON K1R 7Y7 | SPOT PLUS - CANADA POST |
| United Kingdom | 48 Elm Row, EDINBURGH, EH7 4AH | BM GLOBALX LIMITED - #UK161 |
| Ireland | 15 AUNGIER STREET, RATHMINES WEST, DUBLIN, CO DUBLIN D02 DD73 | WIRED - CASH AND DEBIT |
| Hungary | DOBO KORUT 8, KECSKEMET, BACS-KISKUN 6000 | CORNER TRADE KFT. |
| Poland | Al. Jerozlimskie 89 lok. 152 B C D, WARSZAWA, MAZOWIECKIE 02-001 | LITTLE INDIA SHOP - SKLEP LITTLE INDIA |
| Argentina | AVENIDA CORDOBA 947, RETIRO, BUENOS AIRES, CAPITAL FEDERAL 1054 | LATIN EXPRESS FINANCIAL SERVICES ARGENTI |
| Argentina | GENERAL MARIANO ACHA 4092, BUENOS AIRES, CAPITAL FEDERAL 1430 | SAAVEDRA COBRO EXPRESS - #LEAR1-1476 |
Note: The Cash-out flow also displays a locator, but the user is not required to pre-select a location to withdraw funds. After completing the UI flow the user can visit any participating MoneyGram location to pick up their funds.
Transaction limits
| on/off ramps (production) | Min | Max |
|---|---|---|
| On-ramp (cash-in) | 15 USDC | 950 USDC |
| Off-ramp (cash-out) | 15 USDC | 2,500 USDC |
Off-ramps available in 170+ countries — see the location locator.
Key concepts
Memo: Identifier tying on-chain payments to MoneyGram records. Always use the memo returned in the SEP-24 transaction object (withdraw_memo for cash-out, deposit_memo for cash-in). Learn more
Trustline: Required before a Stellar account can hold USDC. Learn more
Resources
- SEP-1 (TOML)
- SEP-9 (KYC fields)
- SEP-10 (Authentication)
- SEP-24 (Deposit/Withdrawal)
- Stellar Lab
- Stellar Demo Wallet
- Find participating MoneyGram locations (mainnet)
- Partner support: [email protected]
Setting up a Stellar testnet wallet
If you would like to try out the Moneygram Ramps UI right now:
- Generate two testnet Stellar keypairs (authentication and funds) via Stellar Lab.
- Fund accounts with XLM and USDC (create a USDC trustline in Stellar Lab).
- Test with the Stellar Demo Wallet on testnet.
- "Provide a secret key (testnet only)" => use your secret key you just created
- Change "centre.io" to "extmgxanchor.moneygram.com"
- "Select action" => "Sep-24 withdraw" => "Start"
- UI will launch in a pop-up window