> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modempay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Accept Stablecoin Payments

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.

<Note>
  Stablecoin payments <b>do not work in test mode</b>. You need a live API key
  to create and accept USDT/USDC payments.
</Note>

## How it works

<Steps>
  <Step title="Create a payment request">
    Tell us how much to collect (in USD or GMD) and we give you back a payment
    link.
  </Step>

  <Step title="Send your customer to the payment page">
    Point them to the `link` we return. They pay right there.
  </Step>

  <Step title="Get notified when they pay">
    We send you a `charge.succeeded` message telling you how much has come in so
    far.
  </Step>
</Steps>

## Create a payment request

This creates the request and gives you a hosted payment page link to share.

<ParamField body="amount" type="number" required>
  How much you want to collect.
</ParamField>

<ParamField body="currency" type="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`.
</ParamField>

<ParamField body="metadata" type="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.
</ParamField>

<ParamField body="callback_url" type="string">
  A web address where you want to be notified about this payment.
</ParamField>

<ParamField body="expires_at" type="string">
  An optional deadline for the payment (ISO 8601 format).
</ParamField>

<Note>
  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.
</Note>

<CodeGroup>
  ```typescript nodejs theme={null}
  const intent = await modempay.quoteIntents.create({
    amount: 10,
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.modempay.com/v1/minting/intents \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": 10
    }'
  ```
</CodeGroup>

<Note>
  Only `amount` is required. Add `metadata` if you want your own references sent
  back to you in the notifications.
</Note>

**What you get back**

```json json theme={null}
{
  "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:

<Warning>
  **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.
</Warning>

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.
* **`status`** — `partially_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

```bash cURL theme={null}
curl https://api.modempay.com/v1/minting/intents \
  -H "Authorization: Bearer YOUR_API_KEY"
```

## Look up a single payment request

```bash cURL theme={null}
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.

<ParamField body="event" type="string">
  The type of event, e.g. `charge.succeeded`.
</ParamField>

<ParamField body="payload" type="object">
  The payment details. The main fields are below.
</ParamField>

<ParamField body="payload.amount" type="number">
  Total paid toward this payment so far, in USD. Adds up across transfers.
</ParamField>

<ParamField body="payload.status" type="string">
  `partially_paid` until the full amount is in, then `completed`.
</ParamField>

<ParamField body="payload.metadata.usd_amount" type="number">
  The amount you originally asked for, in USD. Compare `amount` against this to
  know when the payment is fully covered.
</ParamField>

<ParamField body="payload.metadata.payment_history" type="array">
  A list of each individual payment received, with `amount_paid_usd`,
  `amount_paid_gmd`, and `timestamp`.
</ParamField>

```json json theme={null}
{
  "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`.

<Warning>
  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.
</Warning>

<Note>
  To confirm a notification really came from Modem Pay (checking the signature
  header), see the [Webhooks guide](/documentation/core/webhooks).
</Note>

## 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.
