Crypto addresses
A permanent USDT address per customer. Whatever they send, whenever they send it, arrives labelled with your own id. No checkout, no exact amount.
A customer can have permanent USDT addresses as well as, or instead of, a naira account. Whatever amount they send, whenever they send it, is credited to you and reported with their customer_reference. No checkout, no exact amount.
Switched on per business by Paymonetra, and only for a business that holds USDT as coin (collect_crypto_as: hold). Customer deposits are credited as USDT, never converted, so the price movement is yours as with any held coin.
Ask for addresses with two optional fields on POST /customer_accounts:
| field | default | meaning |
|---|---|---|
crypto | none | a list of usdt_trc20, usdt_bep20. Asking again adds any not yet issued and returns the rest unchanged |
bank | true | false with crypto set issues addresses only, no naira account |
curl https://api.paymonetra.com/v1/customer_accounts \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"customer_reference":"cus_9f21","customer_name":"Ngozi Nwosu","crypto":["usdt_trc20","usdt_bep20"]}'Every customer object, from create, fetch and list, now carries crypto_addresses, empty when there are none:
{
"customer_reference": "cus_9f21",
"customer_name": "Ngozi Nwosu",
"account_number": "8021234567",
"account_name": "MONETRA - Samifunds (Ngozi Nwosu)",
"bank_name": "Payrep MFB",
"status": "active",
"mode": "live",
"credits": 0,
"collected_total": 0,
"last_credit_at": null,
"created_at": "2026-10-02T10:12:00+01:00",
"crypto_addresses": [
{ "coin": "USDT", "network": "BEP20", "address": "0xa4FB606FA60376E086de0f1B273212a59332c538" },
{ "coin": "USDT", "network": "TRC20", "address": "TQWYubCB6kVFo4xHad8F767sRweL5VXsTW" }
]
}An addresses-only customer ("bank": false) has account_number, account_name and bank_name as null, and credits and collected_total at zero: those two count naira only. In the list they come after every naira account, newest first, so existing paging is unchanged.
201 when anything new was issued, 200 when the customer already had all of it. Status 201 and 200 carry the same body.
| code | http | means |
|---|---|---|
not_enabled | 403 | customer addresses are not switched on for this business, or USDT is not set to hold. Nothing is issued, not even the naira account |
invalid_request | 422 | an unknown crypto value (field: crypto), or bank false with no crypto |
limit_reached | 422 | the business has reached its limit of customers with addresses (1,000 unless raised) |
provider_error | 502 | the address is not ready yet; retry the same request |
conflict | 409 | the same customer is being created by another request; retry |
When a customer pays, you get collection.success once the coin is safely in your balance, usually within a minute:
{
"event": "collection.success",
"data": {
"reference": "pmcd_118_4c1e09b2a7f3d5e1",
"amount": "25.000000", "fee": "0.375000", "net": "24.625000",
"currency": "USDT", "rail": "usdt", "settles_as": "coin",
"coin": "USDT", "coin_network": "TRC20",
"coin_amount": "25.000000", "coin_received": "25.000000", "coin_txid": "",
"customer_reference": "cus_9f21", "customer_name": "Ngozi Nwosu",
"checkout_reference": null,
"status": "paid",
"paid_at": "2026-10-02T10:20:41+01:00"
}
}Same keys as a held USDT checkout payment, so one handler reads both. The fee is your usdt rate. Match on customer_reference; checkout_reference is always null here.
Tell your customer which network. USDT sent on a network other than the address's is not credited automatically. Show the network beside every address.
#Test it
Fires the same collection.success a real deposit does, so your handler is exercised end to end. See simulate_crypto.