Paid.my Merchant API

Accept crypto payments with hosted checkout, invoices, static deposit addresses and automated payouts. All requests and responses are JSON. Monetary amounts are transmitted as strings to preserve decimal precision.

Base URL
https://paid.my/api/v1
OpenAPI spec
/api/openapi.json
Import into Postman, Insomnia or Swagger UI, or generate a client.

Quick start

  1. Create an account and go to Merchant Service → Generate API Key. Pick the Merchant scope. The key is shown once.
  2. Call POST /payment/invoice with an amount — you get back a payment_url.
  3. Redirect your customer there. They pick a coin and pay.
  4. Receive a signed invoice.paid webhook at your callback_url, or poll GET /payment/{track_id}.
curl -X POST https://paid.my/api/v1/payment/invoice \
  -H "merchant-api-key: paidmy_mk_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{"amount":100,"currency":"USD","order_id":"order-1042","callback_url":"https://yoursite.com/webhooks/paidmy"}'

Authentication

Every endpoint except the public common/* endpoints requires an API key sent in a scope-specific request header. Three equivalent forms are accepted: the hyphenated header shown below (recommended), the underscore variant (merchant_api_key — OxaPay-compatible, but many proxies silently strip underscore headers), or Authorization: Bearer <key>. A key only works for its own scope — using a payout key on a payment endpoint returns 401 with This key has scope PAYOUT; MERCHANT is required.

ScopeHeaderLive prefixSandbox prefixUsed for
Merchantmerchant-api-keypaidmy_mk_paidmy_smk_Creating/reading payments, static addresses
Payoutpayout-api-keypaidmy_pk_paidmy_spk_Sending/reading payouts
Generalgeneral-api-keypaidmy_gk_paidmy_sgk_Account-level reads (balances)

Response envelope

Every response — success or failure — uses the same envelope. On failure error is an object and data is empty. Amounts are decimal strings.

{
  "data": {
    "track_id": "9YfKp2hQ7Wm3"
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}
{
  "data": {},
  "message": "amount: Number must be greater than 0",
  "error": {
    "type": "validation_error",
    "key": "amount",
    "message": "amount: Number must be greater than 0"
  },
  "status": 400,
  "version": "1.0"
}
error.typeHTTPMeaning
validation_error400Bad input; key names the field
auth_error401Missing/invalid key or wrong scope
forbidden403Key or feature not permitted
not_found404Unknown track_id / address
conflict409Duplicate order_id or idempotency mismatch
rate_limited429Too many requests — see Retry-After
internal_error500Something went wrong on our side

Idempotency

POST endpoints with side effects (payment/invoice, payment/static-address, payout) accept an Idempotency-Key header. Send any unique string (a UUID) and retries are safe:

  • Same key + same body → the stored response is replayed; no duplicate invoice or payout.
  • Same key + different body → 409 conflict.
  • Same key while the first request is still processing → 409 conflict ("still being processed") — retry shortly.

Always use it for payouts — a network timeout on a payout without a key can otherwise double-send.

Rate limits

Limits are applied per API key. Exceeding one returns 429 with a Retry-After header (seconds).

BucketLimitApplies to
General5 req/s sustained (burst 20)All endpoints unless listed below
Invoice creation60 / minutePOST /payment/invoice
Payout creation10 / minutePOST /payout

Payments

Invoices, hosted checkout and static deposit addresses.

Create an invoice

post/api/v1/payment/invoice
Auth: Merchant key · merchant-api-keyIdempotency-Key supportedRate limit: 60 / minute

Creates a payment and returns a hosted checkout URL. Redirect your customer to payment_url; they pick a coin and network there, and the exact pay amount and deposit address are allocated at that moment.

Request body

FieldTypeDescription
amountrequirednumberInvoice amount in currency. Must be > 0.
currencystringFiat (USD, EUR, GBP) or a crypto code. (default "USD")
lifetimeintegerMinutes until expiry. (15–2880, default 60)
fee_paid_by_payerboolean | "0" | "1"Charge the platform fee to the payer. Default: API key setting, then account setting.
under_paid_coveragenumberAccepted shortfall in percent. Default: API key setting, then account setting. (0–60)
callback_urlstring (uri)Webhook target for this invoice. Default: API key callback URL, then account default.
return_urlstring (uri)Shown to the payer after payment.
order_idstringYour reference. Unique per merchant — reuse returns 409. (≤128 chars)
emailstring (email)Payer email for your records.
descriptionstringShown on the payment page. (≤512 chars)
thanks_messagestringShown after payment completes. (≤512 chars)
to_currency"USDT"Set to USDT to auto-convert the credited amount to USDT once the deposit is swept (no extra Paid.my fee; swap.my spread applies). Omit to use the API key's Auto-convert setting.
sandboxbooleanForce sandbox (implied when using a sandbox key).

Example request

curl -X POST https://paid.my/api/v1/payment/invoice \
  -H "merchant-api-key: paidmy_mk_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{"amount":100,"currency":"USD","order_id":"order-1042","callback_url":"https://yoursite.com/webhooks/paidmy"}'

Response data

FieldTypeDescription
track_idstringPayment identifier.
payment_urlstring (uri)Hosted checkout URL.
expired_atintegerExpiry. Unix timestamp (seconds).
dateintegerCreation. Unix timestamp (seconds).
{
  "data": {
    "track_id": "9YfKp2hQ7Wm3",
    "payment_url": "https://paid.my/pay/9YfKp2hQ7Wm3",
    "expired_at": 1755172800,
    "date": 1755169200
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
409order_id already used, or idempotency-key body mismatch.
429Rate limited (rate_limited); honour the Retry-After header.

Create a white-label payment

post/api/v1/payment/white-label
Auth: Merchant key · merchant-api-keyIdempotency-Key supportedRate limit: 60 / minute

Creates a payment and locks the pay currency in one call, returning the deposit address, exact pay_amount, memo, locked rate and a qr_code image, so you can render the payment screen inside your own checkout or app instead of redirecting to the hosted page. Everything downstream (detection, crediting, webhooks, GET /payment/{track_id}) is identical to a normal invoice. The address is reserved only for lifetime; funds sent after expiry are not credited automatically.

Request body

FieldTypeDescription
amountrequirednumberInvoice amount in currency. Must be > 0.
currencystringFiat (USD, EUR, GBP) or a crypto code. (default "USD")
pay_currencyrequiredstringCrypto the payer will send.
networkstringChain id. Required when pay_currency is available on more than one network (the error lists them).
lifetimeintegerMinutes the address stays valid. (15–2880, default 60)
fee_paid_by_payerboolean | "0" | "1"Charge the platform fee to the payer. Default: API key setting, then account setting.
under_paid_coveragenumberAccepted shortfall in percent. Default: API key setting, then account setting. (0–60)
callback_urlstring (uri)Webhook target for this payment.
order_idstringYour reference. Unique per merchant; reuse returns 409. (≤128 chars)
emailstring (email)Payer email for your records.
descriptionstringYour note. (≤512 chars)
to_currency"USDT"Set to USDT to auto-convert the credited amount to USDT once swept. Omit to use the API key's Auto-convert setting.
sandboxbooleanForce sandbox (implied when using a sandbox key).

Example request

curl -X POST https://paid.my/api/v1/payment/white-label \
  -H "merchant-api-key: paidmy_mk_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{"amount":100,"currency":"USD","pay_currency":"USDT","network":"TRON","order_id":"order-1043"}'

Response data

FieldTypeDescription
track_idstringPayment identifier.
status"new" | "waiting" | "paying" | "paid" | "underpaid" | "expired" | "refunding" | "refunded" | "failed"See Payment statuses.
amountstringInvoice amount in currency. Decimal string — never parse into a float for accounting.
currencystringInvoice currency (fiat or crypto code).
pay_currencystring | nullCrypto the payer selected.
networkstring | nullChain the payer selected.
pay_amountstringExact crypto amount requested from the payer. Decimal string — never parse into a float for accounting.
received_amountstringCrypto received so far. Decimal string — never parse into a float for accounting.
credited_amountstringAmount credited to your balance after fees. Decimal string — never parse into a float for accounting.
fee_amountstringPlatform fee. Decimal string — never parse into a float for accounting.
ratestringLocked crypto/fiat rate. Decimal string — never parse into a float for accounting.
addressstring | nullDeposit address allocated for this payment.
memostring | nullMemo/tag where the chain needs one.
fee_paid_by_payer"0" | "1"1 if the payer covered the fee.
under_paid_coveragenumberAccepted shortfall, percent (0–60).
lifetimeintegerLifetime in minutes.
order_idstring | nullYour order reference.
emailstring | nullPayer email.
descriptionstring | nullDescription shown on the payment page.
sandboxbooleanTrue for sandbox payments.
auto_convertbooleanWhether the credited amount is auto-converted to USDT.
conversionobject | nullAuto-convert progress; null when not enabled or not yet credited. Conversion starts after the deposit is swept to our hot wallet (typically within ~15 minutes of payment).
expired_atintegerExpiry. Unix timestamp (seconds).
dateintegerCreation. Unix timestamp (seconds).
paid_atinteger | nullPaid time. Unix timestamp (seconds).
txsobject[]On-chain deposits matched to this payment.
payment_urlstring (uri)Hosted checkout URL, should you still want to offer it.
qr_codestring (uri)SVG QR image of the wallet-app payment URI for address / pay_amount.
{
  "data": {
    "track_id": "9YfKp2hQ7Wm3",
    "status": "waiting",
    "amount": "100",
    "currency": "USD",
    "pay_currency": "USDT",
    "network": "TRON",
    "pay_amount": "101.01",
    "received_amount": "0",
    "credited_amount": null,
    "fee_amount": "1.01",
    "rate": "0.9999",
    "address": "TQmZ1hVh7…",
    "memo": null,
    "fee_paid_by_payer": 1,
    "under_paid_coverage": 0,
    "lifetime": 60,
    "order_id": "order-1043",
    "email": null,
    "description": null,
    "sandbox": false,
    "expired_at": 1755172800,
    "date": 1755169200,
    "paid_at": null,
    "txs": [],
    "payment_url": "https://paid.my/pay/9YfKp2hQ7Wm3",
    "qr_code": "https://paid.my/api/pay/9YfKp2hQ7Wm3/qr"
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error. error.key is pay_currency (not accepted / rate unavailable) or network (ambiguous or unavailable; the message lists valid networks).
401Missing/invalid API key, or wrong scope (auth_error).
409order_id already used, or idempotency-key body mismatch.
429Rate limited (rate_limited); honour the Retry-After header.

Get a payment

get/api/v1/payment/{track_id}
Auth: Merchant key · merchant-api-key

Path parameters

NameTypeDescription
track_idstringPayment identifier.

Example request

curl https://paid.my/api/v1/payment/9YfKp2hQ7Wm3 \
  -H "merchant-api-key: paidmy_mk_…"

Response data

FieldTypeDescription
track_idstringPayment identifier.
status"new" | "waiting" | "paying" | "paid" | "underpaid" | "expired" | "refunding" | "refunded" | "failed"See Payment statuses.
amountstringInvoice amount in currency. Decimal string — never parse into a float for accounting.
currencystringInvoice currency (fiat or crypto code).
pay_currencystring | nullCrypto the payer selected.
networkstring | nullChain the payer selected.
pay_amountstringExact crypto amount requested from the payer. Decimal string — never parse into a float for accounting.
received_amountstringCrypto received so far. Decimal string — never parse into a float for accounting.
credited_amountstringAmount credited to your balance after fees. Decimal string — never parse into a float for accounting.
fee_amountstringPlatform fee. Decimal string — never parse into a float for accounting.
ratestringLocked crypto/fiat rate. Decimal string — never parse into a float for accounting.
addressstring | nullDeposit address allocated for this payment.
memostring | nullMemo/tag where the chain needs one.
fee_paid_by_payer"0" | "1"1 if the payer covered the fee.
under_paid_coveragenumberAccepted shortfall, percent (0–60).
lifetimeintegerLifetime in minutes.
order_idstring | nullYour order reference.
emailstring | nullPayer email.
descriptionstring | nullDescription shown on the payment page.
sandboxbooleanTrue for sandbox payments.
auto_convertbooleanWhether the credited amount is auto-converted to USDT.
conversionobject | nullAuto-convert progress; null when not enabled or not yet credited. Conversion starts after the deposit is swept to our hot wallet (typically within ~15 minutes of payment).
expired_atintegerExpiry. Unix timestamp (seconds).
dateintegerCreation. Unix timestamp (seconds).
paid_atinteger | nullPaid time. Unix timestamp (seconds).
txsobject[]On-chain deposits matched to this payment.
{
  "data": {
    "track_id": "9YfKp2hQ7Wm3",
    "status": "paid",
    "amount": "100",
    "currency": "USD",
    "pay_currency": "USDT",
    "network": "TRON",
    "pay_amount": "100.5",
    "received_amount": "100.5",
    "credited_amount": "99.495",
    "fee_amount": "1.005",
    "rate": "0.999",
    "address": "TQmZ1hVh7…",
    "memo": null,
    "fee_paid_by_payer": 1,
    "under_paid_coverage": 0,
    "lifetime": 60,
    "order_id": "order-1042",
    "email": "payer@example.com",
    "description": "Pro plan",
    "sandbox": false,
    "expired_at": 1755172800,
    "date": 1755169200,
    "paid_at": 1755169980,
    "txs": [
      {
        "tx_hash": "6f9c2c6f6f0a…",
        "amount": "100.5",
        "confirmations": 21,
        "status": "credited",
        "date": 1755169900
      }
    ]
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
404Unknown track_id.
429Rate limited (rate_limited); honour the Retry-After header.

List payments

get/api/v1/payment
Auth: Merchant key · merchant-api-key

Query parameters

NameTypeDescription
pageintegerPage number. (1–…, default 1)
sizeintegerPage size. (1–200, default 10)
statusstringFilter by status, e.g. paid, waiting.
order_idstringFilter by your order reference.
sort_type"asc" | "desc"Sort by creation date. (default "desc")

Example request

curl https://paid.my/api/v1/payment?status=paid \
  -H "merchant-api-key: paidmy_mk_…"

Response data

FieldTypeDescription
listobject[]
metaobject
{
  "data": {
    "list": [
      {
        "track_id": "9YfKp2hQ7Wm3",
        "status": "paid",
        "amount": "100",
        "currency": "USD"
      }
    ],
    "meta": {
      "page": 1,
      "last_page": 4,
      "total": 38
    }
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
429Rate limited (rate_limited); honour the Retry-After header.

Accepted currencies

get/api/v1/payment/accepted-currencies
Auth: Merchant key · merchant-api-key

Currency codes your account accepts (all enabled currencies, intersected with your dashboard allowlist if set).

Example request

curl https://paid.my/api/v1/payment/accepted-currencies \
  -H "merchant-api-key: paidmy_mk_…"

Response data

FieldTypeDescription
liststring[]
{
  "data": {
    "list": [
      "BTC",
      "ETH",
      "USDT",
      "USDC",
      "TRX",
      "LTC",
      "SOL",
      "BNB",
      "POL",
      "GRAM"
    ]
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
429Rate limited (rate_limited); honour the Retry-After header.

Create a static address

post/api/v1/payment/static-address
Auth: Merchant key · merchant-api-keyIdempotency-Key supported

A permanent deposit address. Every confirmed deposit is credited individually (static-address fee applies) and fires a static_address.deposit webhook.

Request body

FieldTypeDescription
networkrequiredstringChain id, e.g. TRON, ETH, BSC, POLYGON, BTC, LTC, SOL, TON.
currencystringRestrict to one currency; omit to accept any enabled token on the chain.
callback_urlstring (uri)Webhook target for deposits.
order_idstringReporting only. (≤128 chars)
emailstring (email)For your records.
to_currency"USDT"Set to USDT to auto-convert every credited deposit to USDT. Omit to use the API key's Auto-convert setting.

Example request

curl -X POST https://paid.my/api/v1/payment/static-address \
  -H "merchant-api-key: paidmy_mk_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{"network":"TRON","currency":"USDT"}'

Response data

FieldTypeDescription
track_idstringIdentifier.
networkstringChain id.
currencystring | nullCurrency or null.
addressstringDeposit address.
memostring | nullMemo/tag.
dateintegerCreation. Unix timestamp (seconds).
{
  "data": {
    "track_id": "Ab3dEfGh1jKl",
    "network": "TRON",
    "currency": "USDT",
    "address": "TVxq8…",
    "memo": null,
    "date": 1755169200
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
429Rate limited (rate_limited); honour the Retry-After header.

List static addresses

get/api/v1/payment/static-address
Auth: Merchant key · merchant-api-key

Query parameters

NameTypeDescription
pageintegerPage number. (1–…, default 1)
sizeintegerPage size. (1–200, default 10)
networkstringFilter by chain id.

Example request

curl https://paid.my/api/v1/payment/static-address?network=… \
  -H "merchant-api-key: paidmy_mk_…"

Response data

FieldTypeDescription
listobject[]
metaobject
{
  "data": {
    "list": [
      {
        "track_id": "Ab3dEfGh1jKl",
        "network": "TRON",
        "currency": "USDT",
        "address": "TVxq8…",
        "memo": null,
        "have_tx": true,
        "sandbox": false,
        "date": 1755169200
      }
    ],
    "meta": {
      "page": 1,
      "last_page": 1,
      "total": 3
    }
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
429Rate limited (rate_limited); honour the Retry-After header.

Revoke a static address

post/api/v1/payment/static-address/revoke
Auth: Merchant key · merchant-api-key

Deposits arriving after revocation are not credited automatically.

Request body

FieldTypeDescription
addressrequiredstringThe address to revoke.

Example request

curl -X POST https://paid.my/api/v1/payment/static-address/revoke \
  -H "merchant-api-key: paidmy_mk_…" \
  -H "content-type: application/json" \
  -d '{"address":"TVxq8…"}'

Response data

FieldTypeDescription
revokedbooleanAlways true on success.
{
  "data": {
    "revoked": true
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
404Address not found.
429Rate limited (rate_limited); honour the Retry-After header.

Payouts

Send crypto from your balance to any external address.

Send a payout

post/api/v1/payout
Auth: Payout key · payout-api-keyIdempotency-Key supportedRate limit: 10 / minute

Sends crypto to an external address. amount + fee is held from your available balance immediately and settled on confirmation (or released on failure). Use an Idempotency-Key — retries are then safe.

Request body

FieldTypeDescription
addressrequiredstringDestination address (the recipient's wallet).
currencyrequiredstringCurrency code.
amountrequirednumberAmount to deliver — the fee is charged on top.
networkstringChain id. Required when the currency exists on more than one enabled network.
memostringMemo/tag for chains that use one (e.g. TON exchanges). (≤256 chars)
callback_urlstring (uri)Webhook target for this payout.
descriptionstringYour note. (≤512 chars)

Example request

curl -X POST https://paid.my/api/v1/payout \
  -H "payout-api-key: paidmy_pk_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{"address":"TXYZ…","currency":"USDT","amount":25,"network":"TRON"}'

Response data

FieldTypeDescription
track_idstringPayout identifier.
statusstringAlways processing on creation.
{
  "data": {
    "track_id": "Zx9v8W7uQ6Tp",
    "status": "processing"
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error. error.key is amount (insufficient balance), network (unavailable pair), below_minimum, or not_allowlisted.
401Missing/invalid API key, or wrong scope (auth_error).
409Idempotency-key body mismatch or request still processing.
429Rate limited (rate_limited); honour the Retry-After header.

List payouts

get/api/v1/payout
Auth: Payout key · payout-api-key

Query parameters

NameTypeDescription
pageintegerPage number. (1–…, default 1)
sizeintegerPage size. (1–200, default 10)
statusstringFilter by status.

Example request

curl https://paid.my/api/v1/payout?status=paid \
  -H "payout-api-key: paidmy_pk_…"

Response data

FieldTypeDescription
listobject[]
metaobject

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
429Rate limited (rate_limited); honour the Retry-After header.

Estimate the fee

post/api/v1/payout/estimate-fee
Auth: Payout key · payout-api-key

Request body

FieldTypeDescription
currencyrequiredstringCurrency code.
networkrequiredstringChain id.
amountrequirednumberAmount to deliver.

Example request

curl -X POST https://paid.my/api/v1/payout/estimate-fee \
  -H "payout-api-key: paidmy_pk_…" \
  -H "content-type: application/json" \
  -d '{"currency":"USDT","network":"TRON","amount":25}'

Response data

FieldTypeDescription
currencystringCurrency.
networkstringChain.
amountnumberRequested amount.
feenumberFlat network fee.
total_debitnumberAmount + fee held from your balance.
min_payoutnumberMinimum payout on this network.
{
  "data": {
    "currency": "USDT",
    "network": "TRON",
    "amount": 25,
    "fee": 1,
    "total_debit": 26,
    "min_payout": 2
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
429Rate limited (rate_limited); honour the Retry-After header.

Get a payout

get/api/v1/payout/{track_id}
Auth: Payout key · payout-api-key

Path parameters

NameTypeDescription
track_idstringPayout identifier.

Example request

curl https://paid.my/api/v1/payout/9YfKp2hQ7Wm3 \
  -H "payout-api-key: paidmy_pk_…"

Response data

FieldTypeDescription
track_idstringPayout identifier.
status"processing" | "pending" | "confirming" | "confirmed" | "canceled" | "rejected" | "failed"See Payout statuses.
amountstringAmount delivered to the destination. Decimal string — never parse into a float for accounting.
feestringNetwork fee charged on top. Decimal string — never parse into a float for accounting.
currencystringCurrency code.
networkstringChain id.
addressstringDestination address.
memostring | nullMemo/tag.
tx_hashstring | nullOn-chain transaction hash once broadcast.
sandboxbooleanTrue for sandbox payouts.
dateintegerCreation. Unix timestamp (seconds).
confirmed_atinteger | nullConfirmation time. Unix timestamp (seconds).
{
  "data": {
    "track_id": "Zx9v8W7uQ6Tp",
    "status": "confirmed",
    "amount": "25",
    "fee": "1",
    "currency": "USDT",
    "network": "TRON",
    "address": "TXYZ…",
    "memo": null,
    "tx_hash": "b1946ac9…",
    "sandbox": false,
    "date": 1755169200,
    "confirmed_at": 1755169500
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
404Unknown track_id.
429Rate limited (rate_limited); honour the Retry-After header.

General

Account-level reads — balances.

Account balances

get/api/v1/general/account/balance
Auth: General key · general-api-key

Every currency balance on your account. available is spendable; pending is held by in-flight payouts and swaps. usd is an indicative valuation at current display prices, not a settlement rate. Sandbox keys return sandbox balances.

Example request

curl https://paid.my/api/v1/general/account/balance \
  -H "general-api-key: paidmy_gk_…"

Response data

FieldTypeDescription
balancesobjectKeyed by currency symbol. Includes zero rows for every enabled currency.
total_usdstringSum of all usd values. Decimal string — never parse into a float for accounting.
sandboxbooleanTrue when queried with a sandbox key.
{
  "data": {
    "balances": {
      "BTC": {
        "available": "0.0125",
        "pending": "0",
        "usd": "802.88"
      },
      "USDT": {
        "available": "120.5",
        "pending": "25",
        "usd": "120.38"
      },
      "GRAM": {
        "available": "0",
        "pending": "0",
        "usd": "0.00"
      }
    },
    "total_usd": "923.26",
    "sandbox": false
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Errors

StatusWhen
400Validation error (error.type = validation_error, error.key names the field).
401Missing/invalid API key, or wrong scope (auth_error).
429Rate limited (rate_limited); honour the Retry-After header.

Common

Public reference data — no API key required.

Supported currencies

get/api/v1/common/currencies
Public · no API key

Every enabled currency with its networks, minimums and payout fees. Public — no API key.

Example request

curl https://paid.my/api/v1/common/currencies

Response data

{
  "data": {
    "USDT": {
      "symbol": "USDT",
      "name": "Tether",
      "status": true,
      "networks": {
        "TRON": {
          "network": "TRON",
          "name": "TRON",
          "required_confirmations": 1,
          "contract_address": "TR7NHqje…",
          "deposit_min": "0.5",
          "withdraw_min": "2",
          "withdraw_fee": "1"
        }
      }
    }
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Prices

get/api/v1/common/prices
Public · no API key

USD price per unit for every enabled currency, as strings. Public — no API key.

Example request

curl https://paid.my/api/v1/common/prices

Response data

{
  "data": {
    "BTC": "64230.10",
    "ETH": "3120.55",
    "USDT": "1.00",
    "GRAM": "1.40"
  },
  "message": "Success",
  "error": null,
  "status": 200,
  "version": "1.0"
}

Webhooks

When an invoice, static-address deposit, or payout changes state, Paid.my POSTs a JSON payload to the relevant callback_url (or your merchant default from Settings).

Signature

Every delivery is signed with HMAC-SHA512 over the raw request body, keyed with your webhook secret (Settings — a dedicated secret, not an API key). The lowercase hex digest is sent in two headers with the same value: HMAC (OxaPay-compatible) and paidmy-signature. Also sent: paidmy-event (event type) and paidmy-delivery-id (unique per delivery — use it to de-duplicate). Compute the HMAC over the raw bytes, never a re-serialized object.

const crypto = require("node:crypto");

function verify(rawBody, secret, signatureHeader) {
  const expected = crypto
    .createHmac("sha512", secret)
    .update(rawBody, "utf8")
    .digest("hex");
  return (
    expected.length === signatureHeader.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
  );
}

// Express example — keep the raw body!
app.post("/webhooks/paidmy", express.raw({ type: "*/*" }), (req, res) => {
  if (!verify(req.body, process.env.PAIDMY_WEBHOOK_SECRET, req.get("HMAC") ?? ""))
    return res.status(401).end();
  const event = JSON.parse(req.body);
  // event.type: "invoice" | "static_address" | "payout"; event.status: "Paid" …
  res.sendStatus(200);
});

Delivery and retries

Respond with any 2xx within 10 seconds. On failure or timeout the delivery is retried:

AttemptDelay after previous failure
21 minute
33 minutes
430 minutes
53 hours
69 hours

After the ladder is exhausted the delivery is marked EXHAUSTED; re-send it from the dashboard (Payments → open a payment → Webhook deliveries → Resend). Deliveries are at-least-once.

Events

invoice.paid / invoice.paying / invoice.underpaid

POSTed to the invoice's callback_url (or your default) when a payment changes state. Signed with HMAC-SHA512 of the raw body in the HMAC and paidmy-signature headers; paidmy-event carries the event type, paidmy-delivery-id is unique per delivery. Respond 2xx within 10s.

{
  "type": "invoice",
  "track_id": "9YfKp2hQ7Wm3",
  "status": "Paid",
  "amount": "100",
  "currency": "USD",
  "pay_currency": "USDT",
  "pay_amount": "100.5",
  "received_amount": "100.5",
  "fee_paid_by_payer": 1,
  "under_paid_coverage": 0,
  "order_id": "order-1042",
  "email": "payer@example.com",
  "description": "Pro plan",
  "sandbox": false,
  "date": 1755169900
}

static_address.deposit

A deposit on a static address was credited.

{
  "type": "static_address",
  "track_id": "Ab3dEfGh1jKl",
  "status": "Paid",
  "address": "TVxq8…",
  "amount": "50",
  "credited_amount": "49",
  "fee": "1",
  "currency": "USDT",
  "network": "TRON",
  "tx_hash": "6f9c2c6f6f0a…",
  "sandbox": false,
  "date": 1755169900
}

payout.confirmed / payout.failed

For payout.failed, status is Failed and the held amount + fee is returned to your available balance.

{
  "type": "payout",
  "track_id": "Zx9v8W7uQ6Tp",
  "status": "Confirmed",
  "amount": "25",
  "fee": "1",
  "currency": "USDT",
  "network": "TRON",
  "address": "TXYZ…",
  "memo": null,
  "tx_hash": "b1946ac9…",
  "description": null,
  "sandbox": false,
  "date": 1755169950
}

invoice.paying is sent with status: "Paying" when a partial payment is confirmed; invoice.underpaid (status: "Underpaid") when the invoice expires with a partial amount — those funds are not credited automatically.

Sandbox mode

Integrate end-to-end with no real crypto. Enable sandbox in Settings → Sandbox mode, then:

  • Create keys with the Sandbox toggle — they are prefixed paidmy_smk_, paidmy_spk_, paidmy_sgk_.
  • Requests with a sandbox key operate on sandbox invoices, balances and payouts. A live merchant key can also pass "sandbox": true when creating an invoice.
  • Sandbox invoices show a Simulate payment button on the hosted page that fabricates a confirmed deposit and runs the full crediting + webhook pipeline.
  • Sandbox activity is flagged "sandbox": true in every response and webhook; balances are segregated from live.

Fees

ProductDefault feeNotes
Invoices1% (100 bps)fee_paid_by_payer adds it to the payer's amount instead of deducting from your credit
Static addresses2% (200 bps)Charged per credited deposit
PayoutsFlat per networkQuote with POST /payout/estimate-fee, or read withdraw_fee from GET /common/currencies
Auto-convert to USDT0%Enable per API key or pass to_currency: "USDT". Runs once the deposit is swept (typically within ~15 min); only swap.my's exchange spread applies. Progress is in the payment's conversion field.

Status reference

Payment statuses

StatusMeaning
newInvoice created; payer has not picked a currency yet
waitingCurrency selected; waiting for funds at the deposit address
payingPartial/unconfirmed payment detected; waiting for the rest or confirmations
paidFully paid and credited to your balance (terminal)
underpaidExpired with less than the required amount received (terminal)
expiredLifetime elapsed with no payment (terminal)
refundingRefund in progress
refundedRefund completed (terminal)
failedProcessing failure (terminal)

Webhook payloads capitalize the status (Paid, Paying, Underpaid).

Payout statuses

StatusMeaning
processingAccepted; funds held; queued for broadcast
pendingTransaction being prepared/broadcast
confirmingBroadcast; waiting for confirmations
confirmedConfirmed on-chain; fee settled (terminal)
canceledCanceled before broadcast; hold released (terminal)
rejectedRejected by review; hold released (terminal)
failedBroadcast/processing failed; hold released (terminal)