FastPay API documentation
Accept crypto on your website or Telegram bot: ten currencies across seven networks, prices always in USD, one hosted checkout and one signed webhook — plus a no-code path with payment links. All requests and responses are JSON over HTTPS.
https://fast-pay.moneyAuthorization: Bearer YOUR_API_KEYContent-Type: application/jsonGetting API keys
- Create a free account — only an email is needed.
- Open Business → + New merchant and connect your website or Telegram bot.
- Websites confirm domain ownership with a DNS TXT record, a meta tag or a file — any one of the three.
- After a quick review your merchant becomes Active and the API key starts working.
The fpk_… key is shown once at creation. Store it on your server —
never expose it in browser code. A lost key can be regenerated in the merchant card at any time; the previous key is revoked immediately.
The webhook secret for signature checks lives in the same card.
Request format
Send the API key in the Authorization header on every request. Only merchants in the
Active status can call the API.
Authorization: Bearer fpk_your_api_key
Content-Type: application/jsonSuccessful responses return HTTP 200 with a JSON body. Errors return a matching 4xx/5xx status and
a JSON object with a single error field — see Errors & limits.
Payments — getting started
The flow is the same for a website and a bot:
- Create an invoice from your backend when the customer checks out.
- Redirect the customer to the returned
url— the hosted FastPay checkout. - The customer picks a currency (USDT, USDC, DAI, BNB, ETH, SOL, TON, TRX, POL, AVAX) and network, then sends the exact amount shown on the checkout.
- We detect the transfer on-chain, credit your Business balance minus the service fee and mark the invoice paid.
- Your server gets a signed webhook — or you poll the invoice status.
Set a Support URL in your merchant card: a customer whose payment fell short sees it on the checkout together with the payment ID, so they can reach you instead of guessing.
Who pays the service fee is your choice per merchant — My business (deducted from the credit) or Customer (added on top at checkout). Switch it in the merchant card.
Create an invoice
/api/v1/invoice| Field | Type | |
|---|---|---|
amount | number | required Invoice amount in USD, 0.01 – 100 000. |
order_id | string | Optional. Your order reference, up to 64 characters. Returned in the webhook. |
description | string | Optional. Shown to the customer on the checkout, up to 200 chars. |
return_url | string | Optional. Where the customer goes after a successful payment. |
underpaid_tolerance_percent | number | Optional, 0–5. A payment short of the invoice amount by at most this percentage still settles the invoice — useful when customers pay from exchanges that deduct their own fee. If omitted, the merchant's own setting applies (Business → merchant card), and failing that the platform default. |
test | boolean | Optional. Creates a test invoice: the checkout offers a
simulated payment and the webhook carries test: true; no funds move. |
curl -X POST https://fast-pay.money/api/v1/invoice \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 20,
"order_id": "ORDER-1001",
"description": "Premium plan",
"return_url": "https://yourstore.com/thanks"
}'{
"invoice": {
"uuid": "6e249771-4ced-4a9d-9c67-2efb1876070f",
"url": "https://fast-pay.money/pay/6e249771-4ced-4a9d-9c67-2efb1876070f",
"status": "pending",
"amount": "20.00",
"currency": "USD",
"order_id": "ORDER-1001",
"expires_at": "2026-07-25 05:08:16",
"created_at": "2026-07-25 04:08:16"
}
}Redirect your customer to url. The invoice stays payable for 60 minutes by default; the exact deadline is always in expires_at.
Invoice information
/api/v1/invoice?uuid=<uuid>curl "https://fast-pay.money/api/v1/invoice?uuid=6e249771-4ced-4a9d-9c67-2efb1876070f" \
-H "Authorization: Bearer YOUR_API_KEY"{
"invoice": {
"status": "paid",
"asset": "USDT",
"network": "bep20",
"payment_amount": "20.00",
"paid_amount": "20.00",
"fee": "0.10",
"tx_hash": "0xc267…a02c",
"paid_at": "2026-07-25 04:12:31"
}
}Amount formats: stablecoins are quoted with 2 decimals, other coins with 8. tx_hash is null for balance-detected coins.
Payment statuses
| Status | |
|---|---|
| pending | Waiting for the customer to pay. The checkout is open. |
| paid | Payment detected and credited to your Business balance. |
| underpaid | Part of the amount arrived. The invoice stays open,
the payer is shown what is missing, and the funds are already credited to your Business balance.
Shown in the dashboard and carried by the underpaid webhook. When polling the API,
watch payment_status (wrong_amount_waiting) — the plain
status field only ever holds pending, paid or
expired, and the same value is mirrored in raw_status. |
| expired | The payment window closed without a payment. |
Alongside status, every invoice and every webhook carries payment_status — a
detailed code meant for automated handling. It distinguishes cases that status merges:
payment_status | Meaning | Credit the customer? |
|---|---|---|
check | Waiting for the payment | no |
paid | Exact amount received | yes |
paid_over | More than required received | yes — see paid_amount |
wrong_amount_waiting | Short payment, the invoice is still open for a top-up | no — hold the order |
wrong_amount | Short payment, the window has closed | no — resolve manually |
cancel | Nothing paid, the window has closed | no |
refund_process / refund_paid | Refund requested / sent | reverse the order |
Credit only on paid and paid_over. An underpayment never
produces a paid event, so a platform that cannot credit partial amounts needs no special
handling: it simply ignores everything else and waits for the customer's top-up, which arrives as a
normal paid event for the same uuid.
A transfer that arrives with a wrong amount, in a different coin or after the grace window is still credited to your Business balance as a plain deposit — customer funds are never dropped. See Underpayments & edge cases for the exact rules.
Underpayments & edge cases
Real customers pay from exchanges and custodial wallets that may deduct their own fee from the sum, send late or pick the wrong coin. The rules below are deterministic; no payment is ever lost.
| Scenario | What happens |
|---|---|
| Exact amount received | Invoice settles as paid; the webhook fires. |
| Overpayment | Invoice settles as paid; the full received amount is credited
to you and reported in paid_amount. |
Underpayment within underpaid_tolerance_percent | Invoice settles as
paid with the actually received amount. Set the tolerance per invoice at creation (0–5%). |
| Underpayment beyond the tolerance | The transfer is attributed to the invoice, credited to your
Business balance and reported as underpaid with an underpaid webhook. The invoice
stays open and the checkout tells the payer exactly how much is missing, shows the transaction hash, the
payment ID and your support link. |
| The customer sends the rest later | Transfers accumulate on the invoice. As soon as the total
reaches the amount due (minus the tolerance), the invoice settles as paid and the
paid webhook is delivered with the full paid_amount. |
| Payment after expiry, within 30 minutes | Still settles the invoice — a transfer broadcast
in time but confirmed late is honoured (grace window). The expired webhook is only sent
after this window closes. |
| Payment after the grace window | Credited to your Business balance as a plain deposit; the
invoice remains expired. |
| A different supported coin sent to the payment address | Credited to your Business balance as a plain deposit; the invoice is not affected. |
Plain deposits are visible in your Business transaction history with the sending transaction hash, so any disputed payment can be traced on-chain.
Webhooks
Set the Webhook URL in your merchant card. We POST a JSON event when an invoice is
paid, when a payment arrives but falls short (status: "underpaid") and when the
invoice expires unpaid (status: "expired", sent after the 30-minute late-payment
grace window closes). All three share one shape. A paid event looks like this:
{
"type": "payment",
"uuid": "6e249771-4ced-4a9d-9c67-2efb1876070f",
"order_id": "ORDER-1001",
"status": "paid",
"amount": "20.00",
"payment_amount": "20.0000",
"paid_amount": "20.0000",
"asset": "USDT",
"network": "bep20",
"tx_hash": "0xc267…a02c",
"fee": "0.1000",
"test": false,
"merchant_id": "21aef00c-6ccf-4f7b-a623-4668ce5af5ac",
"paid_at": "2026-07-25 04:12:31"
}Every delivery is signed: the X-FastPay-Signature header is the hex HMAC-SHA256 of the raw
request body, keyed with your webhook secret. Always verify it:
// Node.js
const crypto = require('crypto');
function isValidWebhook(rawBody, signatureHeader, webhookSecret) {
const expected = crypto.createHmac('sha256', webhookSecret)
.update(rawBody).digest('hex');
const got = Buffer.from(signatureHeader || '', 'hex');
const want = Buffer.from(expected, 'hex');
// Lengths must match before comparing — timingSafeEqual throws otherwise,
// and a forged or missing signature is exactly the mismatched case.
if (got.length !== want.length) return false;
return crypto.timingSafeEqual(got, want);
}<?php // PHP
function is_valid_webhook(string $rawBody, string $signature, string $secret): bool {
return hash_equals(hash_hmac('sha256', $rawBody, $secret), $signature);
}received_amount is the total received for this invoice so far and
remaining_amount is what is still missing — both are present on every event, which is
all you need to handle an underpaid notification: hold the order, tell the customer, or
credit them partially. A later top-up produces a paid event for the same
uuid. Both figures are in the coin the customer is paying with; the same two values in USD
arrive as received_amount_usd and remaining_amount_usd, converted at the rate
locked when the customer picked the currency — so a short payment in LTC or BTC can be read in
dollars without fetching a rate yourself. They are null until a currency is picked.
amount is the invoice face value in USD; payment_amount,
paid_amount and fee are in the currency from asset.
tx_hash can be null for coins settled by balance detection
(BNB, ETH, POL, AVAX, SOL) — do not require it.
Respond with any 2xx to acknowledge. If your endpoint is down we retry with growing
delays from a durable queue, and you can resend any webhook manually from the payment card in the
dashboard. Deliveries can arrive more than once — treat the invoice uuid as your idempotency key.
Testing
Pass "test": true when creating an invoice to get a test invoice: the checkout
shows a TEST MODE banner and a «Simulate payment» button instead of waiting for a
real transfer. Pressing it flips the invoice to paid and delivers a normal signed
webhook with "test": true — no money and no balances move anywhere.
curl -X POST https://fast-pay.money/api/v1/invoice \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "amount": 5, "order_id": "TEST-1", "test": true }'To verify webhook delivery before your own endpoint is ready, set the merchant's Webhook URL to
https://fast-pay.money/webhook-echo: it accepts the POST, answers 200 and stores nothing.
The delivery status is visible on the payment card in the dashboard.
Always check the test field in your webhook handler and skip crediting
when it is true. Test invoices are excluded from your income stats. For an end-to-end
rehearsal with real money, any live invoice from $0.50 works — or try the flow yourself at
the demo shop.
Playground
Try the API right here. Paste your API key (Business → merchant card), create a
test invoice and walk the whole flow — the «Simulate payment» button on the
checkout completes it and delivers your webhook with test: true. The key stays in your
browser only.
Payment links
Payment links accept payments without any integration. Create a fixed-amount payment page in Business → Payment links and share the URL anywhere — chat, e-mail, social. Every click mints a fresh invoice, so any number of customers can pay the same link at the same time. Toggle a link off and it stops working instantly.
Payments made through links appear in the same Payments list and send the same webhooks as API invoices.
Refunds
/api/v1/invoice/refund| Field | Type | |
|---|---|---|
uuid | string | Required. The paid invoice to refund. |
address | string | Optional. Defaults to the payer address we captured on-chain; required only when it could not be detected. |
curl -X POST https://fast-pay.money/api/v1/invoice/refund \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "uuid": "6e249771-4ced-4a9d-9c67-2efb1876070f" }'{
"refund": { "withdrawal_id": "b0c1…", "amount": "19.54", "fee": "0.50", "status": "requested" }
}The amount you were credited for the invoice is debited from your Business balance;
the payer receives it minus the network payout fee. Refunds go out after a quick review, one refund
per invoice. Currently available for USDT payments on TRC-20, BEP-20 and Polygon; the same
button lives on the payment card in Business → Payments. refund_status on the
invoice moves requested → sent; a rejected refund returns the money to your balance
and lets you retry.
Payouts
Withdraw USDT from your balance to any external address on TRON (TRC-20), BSC (BEP-20) or Polygon: dashboard → Personal → Send. Business income moves to Personal with the free internal transfer first.
- Enter the address — the network is detected from its format.
- Review the per-network fee and confirm. The amount plus the fee is reserved from your balance.
- Every payout is individually reviewed, then sent on-chain. You get notifications at each step, and the tx hash appears in your request list.
- If a payout is rejected or fails, the full reserved amount returns to your balance automatically.
Current limits and per-network fees are shown right in the Send form.
Supported currencies & networks
Invoices are always created in USD. On the checkout the customer picks what to pay with; you are credited in the currency they paid.
| Currency | Networks |
|---|---|
| USDT | TRC-20, BEP-20, ERC-20, Polygon, Avalanche |
| USDC | BEP-20, ERC-20, Polygon, Avalanche |
| DAI | BEP-20, ERC-20, Polygon |
| BNB / ETH / POL / AVAX | native coin of its chain |
| TRX | TRON |
| SOL | Solana |
TON (GRAM in the API) | TON |
There is no business minimum — invoices are accepted from the $0.01 accounting
floor; per-shop minimums are your own policy. * Non-stablecoin rates come from the live market and are
locked for the invoice lifetime (60 minutes by default, see expires_at). Coin amounts are calculated to eight decimals, the precision the chains themselves use, and the exact
figure is shown to the customer before they pay. The wallet holds 16 coins across 9 chains for
deposits and storage; BTC and LTC on the checkout are next.
Exchange rates
/api/v1/exchange-ratesReturns indicative USD market rates for every supported currency. Stablecoins are pinned to 1. The rate applied to an invoice is locked at the moment the customer picks the currency and held for the invoice lifetime; this endpoint is for display purposes on your side.
curl "https://fast-pay.money/api/v1/exchange-rates" \
-H "Authorization: Bearer YOUR_API_KEY"{
"base": "USD",
"rates": {
"USDT": "1.00000000",
"USDC": "1.00000000",
"DAI": "1.00000000",
"BNB": "567.33000000",
"ETH": "1869.17000000",
"POL": "0.08000000",
"AVAX": "6.54000000",
"TRX": "0.33000000",
"SOL": "74.20000000",
"GRAM": "1.60000000"
}
}Errors & limits
Every error is returned as JSON with an HTTP status and a single human-readable
error field:
{ "error": "amount must be between 0.01 and 100000 USD" }| Code | Meaning |
|---|---|
400 | Invalid input — the message names the field or the rule. |
401 | Missing or invalid API key, or the merchant is not active. |
404 | Invoice not found or it belongs to another merchant. |
429 | Rate limit exceeded — retry with a backoff. |
5xx | Temporary server issue — retry with a backoff. |
| Endpoint | HTTP | Message |
|---|---|---|
any | 401 | Missing or malformed API key |
| 401 | Invalid API key — also returned while the merchant is not Active. | |
| 429 | Too many attempts, try again later | |
POST /invoice | 400 | amount must be a number |
| 400 | amount must be between 0.01 and 100000 USD | |
| 400 | return_url must be a valid http(s) URL | |
| 400 | underpaid_tolerance_percent must be a number between 0 and 5 | |
GET /invoice | 404 | Invoice not found |
POST /invoice/refund | 404 | Invoice not found |
| 400 | Only paid invoices can be refunded | |
| 400 | Test invoices cannot be refunded | |
| 400 | Refunds are currently available for USDT payments only — contact support for other coins | |
| 400 | Provide a valid payer address for the refund | |
| 400 | A refund for this invoice already exists | |
| 400 | Insufficient Business balance for the refund |
| Endpoint | Requests per minute |
|---|---|
POST /api/v1/invoice | 120 |
GET /api/v1/invoice | 300 |
GET /api/v1/exchange-rates | 300 |
POST /api/v1/invoice/refund | 30 |
From $0.01 to $100 000 per invoice. Need a higher ceiling? Ask us.
Legal
Using the API means you accept our terms: