Intermediate

Integrate with MoneyGram Access

This guide is designed to assist you in integrating MoneyGram Access into your existing application. MoneyGram Access is a product offered by MoneyGram that enables users of third-party applications, such as crypto wallets and exchanges, to perform cash-in (deposit) and cash-out (withdrawal) transactions using Stellar USDC.

To gain access to MoneyGram’s testing and production environments, your business needs to go through an onboarding process. To initiate this process, please reach out to partnerships@stellar.org.

Below are the necessary technical requirements and resources to facilitate a seamless integration:

 

Resources

  • MoneyGram Access Wallet MVP Implementation: This MVP implementation can serve as a reference for building your own integration. The code snippets provided in this document are derived from this project.
  • Stellar Test Anchor: While developing your integration and before accessing MoneyGram’s test environment, you can utilize SDF’s test anchor, which replicates the same APIs as MoneyGram’s service but employs a different asset. The asset information for both Stellar Reference Token (SRT) and USD Coin (USDC) is provided.
  • Stellar Demo Wallet: This application visualizes the API calls required to connect to a Stellar Anchor.
  • Stellar Ecosystem Proposal 24 (SEP-24): MoneyGram has implemented this standardized API protocol for Stellar on and off ramps. It serves as the framework for integrating with MoneyGram Access.
  • Stellar Ecosystem Proposal 10 (SEP-10): MoneyGram has implemented this standardized API protocol for Stellar authentication.

 

Integration Process Overview

Applications seeking to integrate MoneyGram Access must implement the client side of Stellar Ecosystem Proposal 24 (SEP-24). This standardized protocol allows applications to connect with anchors like MoneyGram, facilitating Stellar deposit and withdrawal services through local payment rails.

The following steps will guide you through the development of a functional implementation:

  • This guide assumes your application is initially developed on Stellar’s test network and utilizes MoneyGram’s testing deployment of Access. However, there are no functional differences when deploying the application to Stellar’s public network and using MoneyGram’s production deployment.
  • It’s important to distinguish between custodial and non-custodial applications. Custodial applications, such as centralized exchanges, have access to the private keys of the accounts holding users’ funds on Stellar. On the other hand, non-custodial applications do not possess the private keys and typically create or import a pre-existing Stellar account for each user. The integration process may vary slightly depending on the application type.
  • MoneyGram requires authentication of both the user and the application using Stellar’s SEP-10 protocol. For custodial applications, authentication is based on the public key associated with the account registered with MoneyGram during the onboarding process. Non-custodial applications use the home domain registered with MoneyGram for authentication.
  • Transaction initiation differs for custodial and non-custodial applications. Custodial applications must always include the amount field in the request to initiate a new transaction, as only the application has knowledge of the user’s funds. Non-custodial applications are not required to include this field.
  • MoneyGram requires custodial applications to provide the source and destination Stellar accounts during the onboarding process. For non-custodial applications, the source and destination accounts for each transaction should match the authenticated account via SEP-10.

 

Application Flow and Architecture

This guide assumes a basic client-server architecture for your application. The client component requests resources and initiates actions with the server, which directly communicates with MoneyGram’s server.

The following high-level steps outline the cash-out (withdrawal) transaction process:

Step 1: User initiates a cash-out transaction from the application’s client. Step 2: The application’s server requests a MoneyGram-provided URL for the transaction. Step 3: The application’s client opens the URL in a mobile webview or browser tab. Step 4: MoneyGram prompts the user to provide KYC and transaction information. Step 5: After completing the required steps, the application’s client closes the MoneyGram tab or webview and initiates the disbursement of funds. Step 6: MoneyGram provides a reference number to the user, which can be presented at any MoneyGram cash agent to receive cash in the user’s fiat currency.

This guide focuses on the cash-out flow, but a similar process applies to the deposit flow.

 

Generating Stellar Keypairs

In this section, you will generate at least two Stellar keypairs. One keypair is used to prove your application’s identity during authentication with MoneyGram Access, while another keypair is responsible for holding, sending, and receiving USDC on Stellar. The authentication keypair does not need to be funded, but the funds keypair should reference a funded account on the Stellar network.

To generate Stellar keypairs, follow these steps:

  • Visit Stellar Laboratory and generate two keypairs, one for authentication and one for funds.
  • Ensure secure handling of the secret keys, as they are used for authentication and fund disbursement to MoneyGram.
  • Share the public keys (starting with a G) of both the authentication and funds keypairs with MoneyGram during the onboarding process.
  1. Obtaining XLM & USDC

Many cryptocurrency exchanges support purchasing XLM or USDC on the Stellar network. You can utilize the Anchor Directory maintained by the SDF to explore various on and off-ramps for the Stellar Network.

Once you have purchased XLM and/or USDC on an exchange, you can make a payment to an external account. First, send XLM to create the account, then add a USDC trustline, and finally, send USDC. Creating a trustline to USDC can be done using Stellar Laboratory or any Stellar-enabled wallet application.

In cases where exchanges support XLM but not USDC on Stellar, you can sell XLM for USDC on Stellar’s decentralized exchange (SDEX). Simply send your XLM from the exchange to the funds public key, add a USDC trustline, and execute a sell offer to convert XLM to USDC.

 

Authentication

Authentication combines steps 1 and 2 of the architecture diagram mentioned earlier. When the user initiates a cash-out transaction, the application’s client should request a MoneyGram transaction URL from the application’s server. This triggers the authentication process between your server and MoneyGram’s server, following the SEP-10 protocol.

Ensure that your application’s server has the following information:

import requests
from stellar_sdk import Network
from stellar_sdk.sep.stellar_web_authentication import read_challenge_transaction

def get_token() -> str:
    query = f"{AUTH_URL}?account={AUTH_PUBLIC_KEY}&memo={USER_ID}"
    response = requests.get(query)
    body = response.json()
    challenge = read_challenge_transaction(
        challenge_transaction=body["transaction"],
        server_account_id=MGI_ACCESS_SIGNING_KEY,
        home_domains=MGI_ACCESS_HOST,
        web_auth_domain=MGI_ACCESS_HOST,
        network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE
    )
    challenge.transaction.sign(AUTH_SECRET_KEY)
    post_body = {
        "transaction": challenge.transaction.to_xdr()
    }
    response = requests.post(f"{AUTH_URL}", json=post_body)
    response_body = response.json()
    return response_body["token"]

 

Initiate a Transaction

This section encompasses steps 3 & 4 of the architecture diagram displayed above. The application’s server will make a deposit or withdrawal initiation request to MoneyGram’s server, and MoneyGram will return a transaction ID, which will be used later to poll the transaction’s status, and a transaction URL, which should be returned to the application’s client and opened for the user.

For the purpose of this guide, we will go through the withdrawal case.

Note: MoneyGram requires the amount field in requests for both deposit & withdraw transactions if your application is custodial.

You will need the following pieces of information:

  • The authentication token provided by MoneyGram. This token can only be used for actions associated with the user identified by the ID used in the previous steps.
  • The public key of the keypair the application will use to send funds
  • The language code MoneyGram should render their UI’s content with
  • The amount the user would like to withdraw / cash-out
    • This should be collected from the user prior to initiating this transaction

The following code can be used as a reference for implementing this logic yourself. This code is not necessarily production-ready.

import requests

def initiate_withdraw(token: str, amount: str) -> Tuple[str, str]:
    post_body = {
        "asset_code": ASSET_CODE, # USDC
        "account": FUNDS_STELLAR_KEYPAIR.public_key,
        "lang": "en",
        "amount": amount
    }
    response = requests.post(
        MGI_ACCESS_WITHDRAW_URL,
        json=post_body,
        headers={
            "Authorization": f"Bearer {token}"
        }
    )
    body = response.json()
    return body["url"] + "&callback=postmessage", body["id"]

 

The logic for initiating a deposit transaction looks very similar. See the SEP-24 standard specification for detailed information.

The &callback=postmessage query parameter added to the returned URL is critical; it informs MoneyGram that the application’s client would like to be notified when the user has completed the MGI experience and has requested to close the window. We’ll cover this in more detail in the subsequent section.

 

Listen for the Close Notification

The next step is to open the provided URL in the application’s client using a mobile webview, browser tab, or popup. The user will then go through KYC if they have not before in a prior transaction. In the deposit case, the user may also select a MoneyGram agent location to go to when providing cash.

Finally, when the user is done with the MoneyGram UI, the user will select a button displayed on MoneyGram’s UI and MoneyGram will send a postMessage to the window or app that opened its flow initially. The message sent will be the SEP-24 transaction JSON object that represents the transaction.

Below is a simple JavaScript example listening for a postmessage notification.

webview = window.open(moneygramURL, "webview", "width=500,height=800");
window.addEventListener("message", closeWebView);

function closeWebView(e) {
  webview.close();
  const txJson = e.data.transaction;
  console.log(`Transaction ${txJson.id} is ${txJson.status}`);
}

 

Send or Receive Funds

In withdrawal (or cash-out) transactions, applications must send USDC to the Stellar account MoneyGram specifies. In deposit (cash-in) transactions, applications must monitor their Stellar account for a payment from MoneyGram.

In each case, the transaction submitted to Stellar must have a memo attached to it. This memo is provided by MoneyGram in the withdrawal case, and provided by the application in the deposit case. The memo is an identifier that allows the parties to tie the on-chain payment to the transaction record in the application’s or MoneyGram’s database.

 

Poll Until MoneyGram is Ready

Before the application can send funds or instruct the user to provide cash to a MoneyGram agent, the application should confirm with MoneyGram’s server that the transaction is ready to proceed.

You will need the following information to do so.

This code uses a simple polling mechanism with no bail-out condition. The application’s code should be more robust.

import requests

response_body = poll_transaction_until_status(
    transaction_id,
    token=token,
    until_status="pending_user_transfer_start"
)

def poll_transaction_until_status(
    txid: str,
    token: str,
    until_status: str
) -> dict:
    first_iteration = True
    response_body = None
    status = None
    while status != until_status:
        if first_iteration:
            first_iteration = False
        else:
            time.sleep(1)
        query = f"{MGI_ACCESS_TRANSACTION_URL}?id={txid}"
        response = requests.get(
            query,
            headers={
                "Authorization": f"Bearer {token}"
            }
        )
        response_body = response.json()
        status = response_body["transaction"]["status"]
    return response_body

 

Sending Funds

Once MoneyGram is ready to receive funds, your application should extract the Stellar account and memo to use in the payment transaction, construct a Stellar transaction, and submit it to the Stellar network. You’ll need:

  • A copy of MoneyGram’s transaction object
  • The application’s funds public & secret key

Code for submitting transactions to Stellar should be developed thoughtfully. The SDF has a documentation page dedicated to submitting transactions and handling errors gracefully. Here are a few things you need to keep in mind:

  • Offer a high fee. Your fee should be as high as you would offer before deciding the transaction is no longer worth sending. Stellar will only charge you the minimum necessary to be included in the ledger — you won’t be charged the amount you offer unless everyone else is offering the same amount or greater. Otherwise, you’ll pay the smallest fee offered in the set of transactions included in the ledger.
  • Set a maximum timebound on the transaction. This ensures that if your transaction is not included in a ledger before the set time, you can reconstruct the transaction with a higher offered fee and submit it again with better chances of inclusion.
  • Resubmit the transaction when you get 504 status codes. 504 status codes are just telling you that your transaction is still pending — not that it has been canceled or that your request was invalid. You should simply make the request again with the same transaction to get a final status (either included or expired).

Below is highly simplified code for submitting a payment transaction. It does not use timebounds, handle a transaction’s expiration, or handle 504 status codes.

from stellar_sdk import (
    Server, TransactionBuilder, Network, Asset, IdMemo
)

submit_payment(
   destination=response_body["transaction"]["withdraw_anchor_account"],
   memo=response_body["transaction"]["withdraw_memo"],
   amount=response_body["transaction"]["amount_in"]
)

def submit_payment(destination: str, memo: str, amount: str):
   server = Server()
   account = server.load_account(FUNDS_STELLAR_KEYPAIR.public_key)
   transaction = TransactionBuilder(
       source_account=account,
       network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE,
       base_fee=10000  # this is 0.001 XLM
   ).append_payment_op(
       destination=destination,
       asset=Asset(ASSET_CODE, ASSET_ISSUER),
       amount=amount,
   ).add_memo(
       IdMemo(int(memo))
   ).build()
   transaction.sign(FUNDS_STELLAR_KEYPAIR)
   response = server.submit_transaction(transaction)
   print(f"Stellar-generated transaction ID: {response['id']}")

 

Receiving Funds

Once MoneyGram is ready for the user to drop off cash at an MGI agent (in deposit or cash-in cases), the application’s server should begin monitoring its Stellar account for an inbound USDC payment sent by MoneyGram.

The application should have provided a memo for MoneyGram to use when it initiated the deposit. MoneyGram will attach this memo to the transaction used to send the payment to the application, and the application should use this check the memo of transactions involving it’s account to associate the payment back to the user and the specific transaction.

The best way to monitor payments made to an account is to stream events from Stellar’s payments endpoint. The use of streaming cursors can help ensure you never miss an event, even if your application’s streaming process goes down for a period of time.

Note that this code does not handle path payments or claimable balances, two slightly different forms of payment. At the time of writing, MoneyGram does not use either of these options, but you may want to add support for them in case they do in the future.

from stellar_sdk import Server
from .queries import get_transaction_by_memo

def stream_payments(account: str, cursor: str):
    s = Server()
    payments = s.payments().for_account(account).join("transactions")
    for payment in payments.cursor(cursor).stream():
        if (
            payment["type"] != "payment"
            or payment["from"] == account
            or payment["asset_type"] == "native"
            or payment["asset_code"] != ASSET_CODE
            or payment["asset_issuer"] != ASSET_ISSUER
        ):
            continue
        transaction = get_transaction_by_memo(
            payment["transaction"]["memo"],
            payment["transaction"]["memo_type"]
        )  # DB query
        if not transaction:
            continue
        print(
            f"Payment for deposit transaction {transaction.id} "
            "matched with Stellar transaction "
            f"{payment['transaction']['id']}"
        )

 

Fetch the Reference Number

For deposit or cash-in transactions, MoneyGram does not provide reference numbers. All the user needs to do is drop off cash at the agent location chosen in the MoneyGram UI earlier in the flow, and the application should complete the transaction when a matching payment is detected on Stellar.

For withdrawal or cash-out transactions, MoneyGram provides a reference number in their UI and API once MoneyGram detects the application’s payment of USDC on Stellar. Users should be able to use the application’s client interface to view the reference number directly or find the MoneyGram transaction details page and view it there.

Note that MoneyGram’s transaction details page is protected with a JWT token in the url that expires relatively quickly after being fetched. This means applications must fetch the URL at the time the user requests the page, potentially requiring re-authentication via SEP-10.

from .api import poll_transction_until_status

response_body = poll_transaction_until_status(
    transaction_id,
    "pending_user_transfer_complete"
)
tx_dict = response_body["transaction"]
print(
    f"Transaction reference number {tx_dict['external_transaction_id']} "
    f"also viewable at {tx_dict['more_info_url']}"
)