API reference

Full reference for the Payzum payment API endpoints.

The payzum public API is a REST payment surface. Base URL: https://merchant.payzum.com/v1/ (production); staging/sandbox is https://staging.payzum.com/v1/. All authenticated requests require the x-api-key header. For an interactive explorer, see /api/docs.

Authentication

Pass your merchant API key in the x-api-key request header. Keys are issued per merchant from the dashboard under Settings → API Keys. The plaintext key is shown once at creation time; payzum stores only a SHA-256 hash for verification.

Endpoint summary

| Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /v1/status | none | Liveness probe. | | GET | /v1/currencies | none | List supported (chain, symbol) tuples. | | GET | /v1/estimate | none | Quote price → pay amount (includes minimum-amount fields). | | GET | /v1/min-amount | none | Per-currency minimum payment amount. | | GET | /v1/invoices/:id/status | none | Public buyer-facing invoice status (the pzi_ id is the bearer). See Hosted checkout → Polling status yourself. | | POST | /v1/payment | x-api-key | Create an invoice. | | GET | /v1/payment/:id | x-api-key | Read invoice by id or order_id. | | GET | /v1/payment | x-api-key | Paginated list of invoices. |

Additional endpoint families — /v1/mass-payout, /v1/evm-mass-payout, /v1/export/*, and /v1/x402/* — are documented on their own pages.

GET /v1/status

Unauthenticated liveness probe. Returns { "message": "OK" } on success.

curl -s "$PAYZUM_BASE/v1/status"

GET /v1/currencies

Returns a list of all supported currency (chain, symbol) tuples. No authentication required.

curl -s "$PAYZUM_BASE/v1/currencies" | jq '.currencies | length'

The response contains two representations:

  • currencies — the flat array of currency codes (unchanged and stable). Native coins carry no network suffix, so a symbol like "eth" appears once per network it is native on (ethereum, arbitrum, optimism, base).
  • currencies_detailed — an array of objects, one per (chain, token) tuple:

| Field | Type | Description | |-------|------|-------------| | code | string | Currency code as used in pay_currency. | | symbol | string | Token symbol. | | chain | string | Underlying chain. | | standard | string | Token standard. | | contract_address | string | null | Token contract address; null for native coins. | | decimals | number | Token decimals. | | min_amount_usd | number | Network minimum in USD. |

New integrations should use currencies_detailed — it disambiguates the native-coin symbol collisions in the flat array.

GET /v1/estimate

Quote a fiat amount in a target crypto currency. No authentication required.

Query parameters:

  • amount — fiat amount to convert (e.g. 10)
  • currency_from — source fiat currency (e.g. usd)
  • currency_to — target crypto currency (e.g. usdttrc20)
curl -s "$PAYZUM_BASE/v1/estimate?amount=10&currency_from=usd&currency_to=usdttrc20" | jq .

GET /v1/min-amount

Returns the minimum payment amount for a given currency pair. No authentication required.

Query parameters:

  • currency_from — source fiat currency (e.g. usd)
  • currency_to — target crypto currency (e.g. usdttrc20)

Response fields:

  • currency_from — the source fiat currency
  • currency_to — the target crypto currency
  • min_amount — minimum amount expressed in the target crypto currency
  • min_amount_usd — minimum amount in USD
curl -s "$PAYZUM_BASE/v1/min-amount?currency_from=usd&currency_to=usdttrc20" | jq .

If the invoice price_amount would result in a value below min_amount_usd for the chosen network, the invoice creation returns 400 AMOUNT_BELOW_MINIMUM. Check the live minimum before building the invoice to surface a helpful error to the buyer. See Minimum amounts for per-network floors.

Minimum-amount fields

In addition to the quoted pay amount, GET /v1/estimate now returns:

| Field | Type | Description | |-------|------|-------------| | min_amount_usd | number | Network minimum in USD for the chosen currency. | | below_minimum | boolean | null | true if the quoted amount is below the network minimum. Only computed when currency_from is usd; otherwise null. |

Use below_minimum to warn the buyer before they attempt to create an invoice that would be rejected.

POST /v1/payment

Create an invoice. Returns 201 with the full payment object, including the deposit address and expiration timestamp.

Body parameters:

  • price_amount (number, required, > 0) — invoice amount in the price currency
  • price_currency (string 2–8, required) — fiat currency for the invoice (e.g. usd)
  • pay_currency (string 2–32, required) — crypto currency to accept. Three accepted forms: a currency code (e.g. usdttrc20), a bare symbol combined with the network parameter, or the special value "all" to let the buyer pick the currency on the hosted checkout (see Buyer-selected currency)
  • network (string, optional) — when sent, pay_currency is interpreted as a bare symbol on this network (e.g. pay_currency: "usdt", network: "tron")
  • pricing_mode (optional)"fiat" (default) converts price_amount from price_currency to the pay currency via the rate provider; "direct" treats price_amount as already denominated in pay_currency (then price_currency must be identical to the pay symbol). "direct" is incompatible with pay_currency: "all" — the price cannot be denominated in a currency the buyer hasn't chosen yet; that combination is rejected with 400 INVALID_REQUEST.
  • order_id (string ≤ 255, optional) — your internal order identifier. Uniqueness is not enforced — we strongly recommend sending a unique value: with duplicates, GET /v1/payment/{order_id} returns an arbitrary matching invoice.
  • order_description (string ≤ 2000, optional) — free-text shown on the hosted checkout
  • ipn_callback_url (URL, optional) — URL payzum will POST signed IPN events to
  • purchase_id (string, optional) — accepted for compatibility but ignored; the response always returns purchase_id equal to payment_id
  • success_url / cancel_url (http(s) URL ≤ 2048, optional) — where the hosted checkout sends the buyer after a paid / cancelled-or-expired invoice
curl -X POST "$PAYZUM_BASE/v1/payment" \
  -H "x-api-key: $PAYZUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "price_amount": 49.99,
    "price_currency": "usd",
    "pay_currency": "usdttrc20",
    "order_id": "ORDER-12345",
    "ipn_callback_url": "https://merchant.example.com/payzum/ipn"
  }' | jq .

Idempotency

POST /v1/payment accepts an optional Idempotency-Key header (1–255 characters). Keys are scoped per merchant with a 24-hour TTL. Retrying a request with the same key returns the same original 201 response, with the header X-Payzum-Idempotent-Replay: true set on the replay.

Deduplication is best-effort — the cache is eventually consistent (~60 s), so a fast double-submit can still create two invoices. For a hard guarantee, also deduplicate on your side by order_id.

curl -X POST "$PAYZUM_BASE/v1/payment" \
  -H "x-api-key: $PAYZUM_API_KEY" \
  -H "Idempotency-Key: checkout-ORDER-12345" \
  -H "Content-Type: application/json" \
  -d '{ "price_amount": 49.99, "price_currency": "usd", "pay_currency": "usdttrc20" }'

Response (201 Created)

A successful create returns the full payment object. The identifier is payment_id — persist this value to track the invoice. (There is no id field; the :id path parameter on GET /v1/payment/:id accepts either this payment_id or your merchant order_id.)

{
  "payment_id": "pzi_viawy8vaio26d82n023epiq4",
  "payment_status": "waiting",
  "pay_address": "0x1a2b3c4d5e6f7890a1b2c3d4e5f60718293a4b5c",
  "pay_amount": 49.99,
  "pay_currency": "usdcmatic",
  "network": "polygon",
  "network_precision": 6,
  "smart_contract": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
  "price_amount": 49.99,
  "price_currency": "usd",
  "actually_paid": 0,
  "amount_received": 0,
  "order_id": "ORDER-12345",
  "order_description": null,
  "ipn_callback_url": "https://merchant.example.com/payzum/ipn",
  "purchase_id": "pzi_viawy8vaio26d82n023epiq4",
  "invoice_url": "https://merchant.payzum.com/pay/pzi_viawy8vaio26d82n023epiq4",
  "invoice_type": "payment",
  "time_limit": "01:00:00",
  "expiration_estimate_date": "2026-07-13T12:34:56.000Z",
  "created_at": "2026-07-13T11:34:56.000Z",
  "updated_at": "2026-07-13T11:34:56.000Z",
  "burning_percent": null,
  "payin_extra_id": null,
  "subscriber_email": null,
  "subscription_cycle": null,
  "next_renewal_at": null
}

Response fields:

| Field | Type | Description | |-------|------|-------------| | payment_id | string | Payzum invoice identifier. This is the id — store it to track the invoice. | | payment_status | string | Lifecycle status: waiting, partially_paid, finished, expired, failed (see Invoice lifecycle). New invoices start at waiting. | | pay_address | string | Deposit address the buyer must send funds to. | | pay_amount | number | Amount to pay, denominated in pay_currency. | | pay_currency | string | Currency code the buyer pays in (e.g. usdcmatic). | | network | string | Underlying chain (e.g. polygon, tron, ethereum). | | network_precision | number | Token decimals for pay_currency (e.g. 6 for USDC). | | smart_contract | string | null | Token contract address; null for native coins. | | price_amount | number | Original invoice amount in price_currency. | | price_currency | string | Invoice pricing currency (e.g. usd). | | actually_paid | number | Amount received so far, in pay_currency (0 until a deposit lands). | | amount_received | number | Alias of actually_paid. | | order_id | string | null | Your order identifier, echoed back. | | order_description | string | null | Your order description, echoed back. | | ipn_callback_url | string | null | Where signed IPN events are POSTed. | | purchase_id | string | Equal to payment_id (NowPayments compatibility). | | invoice_url | string | null | Hosted-checkout page for the buyer. | | invoice_type | string | One of payment, donation, subscription, pos. | | time_limit | string | Payment window as HH:MM:SS. | | expiration_estimate_date | string | ISO 8601 timestamp when the invoice expires. | | created_at | string | ISO 8601 creation timestamp. | | updated_at | string | ISO 8601 last-update timestamp. | | burning_percent | null | Reserved; always null. | | payin_extra_id | null | Reserved; always null (memo/tag chains not yet exposed here). | | subscriber_email | string | null | Set only when invoice_type is subscription. | | subscription_cycle | number | null | Set only when invoice_type is subscription. | | next_renewal_at | string | null | Set only when invoice_type is subscription. |

Buyer-selected currency (pay_currency: "all")

Send "pay_currency": "all" to defer the currency choice to the buyer. This is the recommended integration for shopping carts: your plugin never needs a currency picker — the buyer chooses on the hosted checkout, and the choices are always the merchant's accepted-tokens allowlist (configured in Merchants → Settings → Accepted tokens), enforced server-side.

curl -X POST "$PAYZUM_BASE/v1/payment" \
  -H "x-api-key: $PAYZUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "price_amount": 49.99,
    "price_currency": "usd",
    "pay_currency": "all",
    "order_id": "ORDER-12345"
  }' | jq .

Behavior differences from a concrete pay_currency:

  • The 201 response is a draft: pay_address, pay_amount, pay_currency, network, network_precision and smart_contract are null until the buyer picks a currency. payment_id is final and never changes — track it exactly like a normal invoice.
  • invoice_url points at the hosted checkout, which shows a currency selector (searchable, grouped by chain) limited to the merchant's allowlist. When the buyer picks, the draft resolves into a real invoice with the SAME payment_id, and the deposit address + amount appear.
  • GET /v1/payment/:id on an unresolved draft additionally returns accepted_currencies — the allowlist snapshot the buyer can choose from. Each entry is { currency, network, standard, estimated_amount, min_usd, token_id }: currency is the uppercase symbol, network the internal chain name, estimated_amount a number or null, min_usd a number, and token_id a string. estimated_amount is a non-binding spot estimate computed at read time; the binding amount is fixed when the buyer picks. After resolution the response is a normal invoice object.
  • Unresolved drafts do not appear in the GET /v1/payment list; they are still retrievable individually via GET /v1/payment/:id.
  • time_limit / expiration_estimate_date on the draft describe the selection window; the payment window starts when the buyer picks.
  • Incompatible with pricing_mode: "direct" (price can't be denominated in an unknown currency) — the API returns 400 INVALID_REQUEST.

GET /v1/payment/:id

Read a single invoice. The :id path parameter accepts either the payzum payment id or the merchant's order_id.

curl -s -H "x-api-key: $PAYZUM_API_KEY" \
  "$PAYZUM_BASE/v1/payment/inv_abc123" | jq .

GET /v1/payment

Paginated list of invoices for the authenticated merchant.

Query parameters:

  • limit — results per page (integer, default 10, clamped to 1100)
  • page — page number, 0-based (integer, default 0)
  • sortBycreated_at (default) or updated_at; any other value falls back to created_at
  • orderBydesc (default) or asc

There are no offset, status or order_id filters. To look up an invoice by order identifier, use GET /v1/payment/{order_id}.

Response: { "data": [...], "limit", "page", "pagesCount", "total" }. pagesCount is always at least 1. Unresolved pay_currency: "all" drafts are excluded from the listing (they remain retrievable via GET /v1/payment/:id).

curl -s -H "x-api-key: $PAYZUM_API_KEY" \
  "$PAYZUM_BASE/v1/payment?limit=20&page=0&orderBy=desc" | jq '.data[].payment_id'

Monetary field types

Field number formats are frozen for compatibility — mind the types when parsing:

  • In payment objects (POST /v1/payment, GET /v1/payment/:id, list entries): price_amount, pay_amount, actually_paid and amount_received are JSON numbers. SDKs that need exact decimal arithmetic should parse the raw JSON body with a decimal-aware parser.
  • In GET /v1/estimate: estimated_amount is a string decimal (up to 8 decimal places); amount_from and min_amount_usd are numbers; below_minimum is a boolean only when currency_from=usd, otherwise null.
  • In GET /v1/min-amount: min_amount is a number (up to 8 decimal places).
  • In the public buyer status endpoint (GET /v1/invoices/:id/status): amounts are string decimals.

Rate limits

Authenticated merchant requests are limited to 60 requests per 60 seconds per merchant, shared between /v1/* and /legacy/*. Exceeding the budget returns 429 with Retry-After: 60 and X-RateLimit-Limit: 60 headers. There are no X-RateLimit-Remaining / X-RateLimit-Reset headers (platform limitation). See Rate limits.

Error responses

All errors return a JSON body of the form { "statusCode": 400, "code": "ERROR_CODE", "message": "..." } — the envelope always includes the numeric statusCode.

Most common error codes (the full canonical table lives in Error codes):

| Code | HTTP | Meaning | |------|------|---------| | API_KEY_MISSING | 401 | No x-api-key header present. | | MERCHANT_SUSPENDED | 403 | The merchant account is suspended. | | PAYMENT_NOT_FOUND | 404 | No invoice matches the given id or order_id. | | INVALID_REQUEST | 400 | A required parameter is missing or invalid. | | RATE_LIMIT_EXCEEDED | 429 | Rate limit exceeded; retry after Retry-After seconds. |

Versioning & deprecation

/v1 is stable: only additive changes ship on it (new fields, new endpoints). Breaking changes ship only under /v2, with a minimum 6-month deprecation window announced by email and in the dashboard. The official SDKs pin /v1.