Skip to main content
Take payments in USDT and USDC (digital dollars that hold a steady value) and get your money settled as normal cash. You tell us how much to collect, we hand you a ready-made payment page, and we let you know when your customer has paid. You never touch wallets, addresses, or any blockchain details.
Stablecoin payments do not work in test mode. You need a live API key to create and accept USDT/USDC payments.

How it works

1

Create a payment request

Tell us how much to collect (in USD or GMD) and we give you back a payment link.
2

Send your customer to the payment page

Point them to the link we return. They pay right there.
3

Get notified when they pay

We send you a charge.succeeded message telling you how much has come in so far.

Create a payment request

This creates the request and gives you a hosted payment page link to share.
amount
number
required
How much you want to collect.
currency
string
default:"USD"
Either USD or GMD. If you send a GMD amount, we convert it to USD for you at the current exchange rate, so the request itself is always in USD. Defaults to USD.
metadata
object
Any of your own notes you want stored with the request and handed back to you later (for example, your order number). We return it to you untouched.
callback_url
string
A web address where you want to be notified about this payment.
expires_at
string
An optional deadline for the payment (ISO 8601 format).
The minimum is 5 USD. If you send a GMD amount that converts to less than $5, we reject it and tell you the current exchange rate.
const intent = await modempay.quoteIntents.create({
  amount: 10,
});
Only amount is required. Add metadata if you want your own references sent back to you in the notifications.
What you get back
json
{
  "id": "00000000-0000-0000-0000-000000000000",
  "status": "pending",
  "amount": 10,
  "currency": "USD",
  "metadata": {},
  "business_id": "8a8d29ad-7e3c-445d-9732-51893ffcfb7d",
  "account_id": "60bcf944-2c46-409f-9654-a93c36db7276",
  "createdAt": "2026-01-01T00:00:00.000Z",
  "updatedAt": "2026-01-01T00:00:00.000Z",
  "payment_link_id": null,
  "token": null,
  "link": "https://checkout.modempay.com/pay/quote/00000000-000000000000",
  "expiresAt": null,
  "callback_url": null
}
Send your customer to the link. That page handles the payment and accepts USDT or USDC. The request starts off as status: "pending" (waiting for payment).

Getting paid

Your customer pays on the hosted page, so you never deal with the payment method yourself. There’s one thing worth designing around:
A payment might not arrive all at once, and might not match the full amount right away. A customer can pay part of it, or pay across several separate transfers. So track the running total we send you, rather than expecting one single full payment.
Two pieces of information tell you everything you need:
  • amount — the total paid toward this request so far. It adds up across transfers. It is not the amount you originally asked for.
  • statuspartially_paid while the total is still short, and completed once the full amount has arrived.
The amount you originally asked for is stored in metadata.usd_amount. The payment is done when amount reaches metadata.usd_amount, which is also the moment status becomes completed.

List your payment requests

cURL
curl https://api.modempay.com/v1/minting/intents \
  -H "Authorization: Bearer YOUR_API_KEY"

Look up a single payment request

cURL
curl https://api.modempay.com/v1/minting/intents/00000000-000000000000 \
  -H "Authorization: Bearer YOUR_API_KEY"
This gives you back the same details as when you created it, showing the latest state of the request.

Notifications (webhooks)

A webhook is just an automatic message we send to your web address whenever money comes in. When a payment is received, we send a charge.succeeded event. You may get it more than once for the same payment as the running total grows.
event
string
The type of event, e.g. charge.succeeded.
payload
object
The payment details. The main fields are below.
payload.amount
number
Total paid toward this payment so far, in USD. Adds up across transfers.
payload.status
string
partially_paid until the full amount is in, then completed.
payload.metadata.usd_amount
number
The amount you originally asked for, in USD. Compare amount against this to know when the payment is fully covered.
payload.metadata.payment_history
array
A list of each individual payment received, with amount_paid_usd, amount_paid_gmd, and timestamp.
json
{
  "event": "charge.succeeded",
  "payload": {
    "id": "00000000-0000-0000-0000-000000000000",
    "type": "payment",
    "status": "partially_paid",
    "amount": 10,
    "currency": "USD",
    "payment_method": "TRC20",
    "reference": "MP-XXXXXXXX-XXXXXX-XXXXXXXX",
    "transaction_reference": "MP-XXXXXXXX-XXXXXX-XXXXXXXX",
    "account_id": "60bcf944-2c46-409f-9654-a93c36db7276",
    "business_id": "8a8d29ad-7e3c-445d-9732-51893ffcfb7d",
    "test_mode": false,
    "metadata": {
      "quote_id": "11111111-1111-1111-1111-111111111111",
      "usd_amount": 20,
      "gmd_rate": 74.8814,
      "fee": 0.2,
      "fee_currency": "USD",
      "fee_bearer": "business",
      "payment_history": [
        {
          "amount_paid_usd": 10,
          "amount_paid_gmd": 741,
          "timestamp": "2026-01-01T00:00:00.000Z"
        }
      ]
    },
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-01-01T00:00:00.000Z"
  }
}
In this example, amount is 10 against an asked-for metadata.usd_amount of 20, so status is partially_paid. Once the remaining 10 is paid, amount becomes 20 and status becomes completed.
Because a payment can come in several parts, you may get more than one charge.succeeded message for the same payment as amount grows. Build your handler so that getting the same message twice doesn’t double-count anything (this is called being idempotent). Match your records on the payment id (or your own metadata reference), and treat amount as the running total.
To confirm a notification really came from Modem Pay (checking the signature header), see the Webhooks guide.

Quick checklist

  • Save the payment id (and your own reference in metadata) when you create the request.
  • Each time you get a charge.succeeded message, read payload.amount (USD) and compare it to payload.metadata.usd_amount.
  • Treat the payment as done when amount reaches usd_amount or when status is completed.
  • Use payload.metadata.payment_history if you ever need to see the individual payments.
  • Make sure getting the same message twice for the same id doesn’t cause any double-counting.