Crypto Pay API: accept crypto payments in a Telegram bot
A developer's guide to Crypto Bot's Crypto Pay API: token issuance, createInvoice, HMAC webhook verification, supported assets
- Author
- TON Adoption Team · research desk
- Published
Contents20sections
- When Crypto Pay is the right choice
- Step 1. Create an app and get a token
- Step 2. First request — getMe
- Step 3. Create an invoice
- Multi-asset invoice
- Step 4. Webhook on payment
- Signature verification — non-negotiable
- Idempotency
- Step 5. Polling as a fallback
- Supported assets (2026)
- Common errors and how to catch them
- INVOICE_NOT_FOUND
- EXPIRES_IN_INVALID
- Rate moves between create and pay
- Webhook arrives without a signature
- App-level security
- Alternatives and combinations
- Production checklist
- Wrapping up
- Sources
Crypto Pay is an HTTP API from Crypto Bot that lets a Telegram bot, a website or a mini-app accept payments in TON, USDT and ~15 other assets — without running your own backend wallet, without exchange integrations and without any on-chain infrastructure. Tens of thousands of merchants are built on it: sticker stores, bot subscriptions, info-products, VPN services. This article walks through a zero-to-production scenario for a developer: issuing a token, calling createInvoice, verifying webhooks, handling failures and shipping with a production checklist.
When Crypto Pay is the right choice
- Telegram-native audience. Your users already hold balances inside
@CryptoBot(tens of millions of accounts) — one-click checkout with no address copy-paste. - Micro-payments in the $1–50 range. Subscriptions, digital goods, tips, one-off services — anywhere on-chain network fees would kill the unit economics.
- You don’t want to run a hot wallet. Crypto Pay is custodial; you don’t manage a seed phrase on your side — the keys live with the CG team.
- Multi-asset baskets. A single invoice can be marked “TON or USDT or BTC accepted”, and the buyer picks at checkout.
Step 1. Create an app and get a token
- Open
@CryptoBotin Telegram (short linkt.me/send). - Menu → Crypto Pay → My Apps → Create App.
- Name the app (visible to you and on operation logs).
- The bot returns an API token like
12345:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. Store it in a secret manager (.env, Vault, AWS Secrets) — it’s effectively the password to your app’s balance.
Base URLs:
- Production:
https://pay.crypt.bot/api/ - Testnet:
https://testnet-pay.crypt.bot/api/— separate token, issued via@CryptoTestnetBot. Useful for CI / staging.
Auth: the Crypto-Pay-API-Token: <your-token> header on every request.
Step 2. First request — getMe
Verify the token is alive:
GET https://pay.crypt.bot/api/getMe
Crypto-Pay-API-Token: 12345:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Response:
{
"ok": true,
"result": {
"app_id": 12345,
"name": "My Test Shop",
"payment_processing_bot_username": "CryptoBot"
}
}
If "ok": false — inspect error.code. Most common:
401 UNAUTHORIZED— token mismatch (typo, accidental whitespace).400 USER_ID_INVALID— not this method, but you’ll see it ontransferwith a baduser_id.429 TOO_MANY_REQUESTS— limit is 100 requests/second; add backoff.
Step 3. Create an invoice
Standard scenario — a user is buying a “PRO” subscription for $9.99 in USDT:
POST https://pay.crypt.bot/api/createInvoice
Content-Type: application/json
Crypto-Pay-API-Token: 12345:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
{
"currency_type": "crypto",
"asset": "USDT",
"amount": "9.99",
"description": "PRO subscription, 30 days",
"hidden_message": "Thanks! The bot will activate access within a minute.",
"paid_btn_name": "openBot",
"paid_btn_url": "https://t.me/your_bot?start=invoice_paid",
"payload": "{\"user_id\":42,\"plan\":\"pro_30d\"}",
"expires_in": 1800
}
Response:
{
"ok": true,
"result": {
"invoice_id": 528347,
"status": "active",
"hash": "IVtN5sR1xVfH",
"asset": "USDT",
"amount": "9.99",
"pay_url": "https://t.me/CryptoBot?start=IVtN5sR1xVfH",
"bot_invoice_url": "https://t.me/CryptoBot?start=IVtN5sR1xVfH",
"mini_app_invoice_url": "https://t.me/CryptoBot/app?startapp=IVtN5sR1xVfH",
"web_app_invoice_url": "https://crypto-pay.io/invoice/...",
"description": "PRO subscription, 30 days",
"created_at": "2026-05-15T10:21:00Z",
"expiration_date": "2026-05-15T10:51:00Z",
"payload": "{\"user_id\":42,\"plan\":\"pro_30d\"}"
}
}
The integration-relevant fields:
pay_url— what you hand the user as a “Pay” button. Opens@CryptoBotwith the invoice pre-loaded.mini_app_invoice_url— for Telegram Mini Apps (opens inside the bot without leaving).payload— up to 4096 chars, your own memo. Putuser_id,order_id, anything you need echoed back in the webhook. JSON-stringify it — Crypto Pay treats it as opaque.expires_in— TTL in seconds. Default is forever; for micro-payments I recommend 600–3600.
Multi-asset invoice
Want to give the user “TON or USDT” choice? Use the fiat-anchored variant:
{
"currency_type": "fiat",
"fiat": "USD",
"amount": "9.99",
"accepted_assets": "TON,USDT,USDC",
"description": "PRO subscription, 30 days",
"payload": "..."
}
Crypto Pay converts $9.99 into TON and USDT at the moment-of-payment rate — the buyer picks with a button.
Step 4. Webhook on payment
Crypto Pay doesn’t push webhooks by default — you enable them per app. In @CryptoBot → My Apps → your app → Webhooks, set a URL like https://api.example.com/cryptopay/webhook.
Incoming request format:
POST /cryptopay/webhook
Content-Type: application/json
crypto-pay-api-signature: <hex-signature>
{
"update_id": 7723,
"update_type": "invoice_paid",
"request_date": "2026-05-15T10:25:11Z",
"payload": {
"invoice_id": 528347,
"status": "paid",
"hash": "IVtN5sR1xVfH",
"asset": "USDT",
"amount": "9.99",
"paid_amount": "9.99",
"paid_asset": "USDT",
"paid_fiat_rate": "1",
"fee": "0",
"paid_anonymously": false,
"paid_btn_name": "openBot",
"paid_btn_url": "https://t.me/your_bot?start=invoice_paid",
"comment": null,
"payload": "{\"user_id\":42,\"plan\":\"pro_30d\"}",
"paid_at": "2026-05-15T10:25:05Z"
}
}
Signature verification — non-negotiable
The signature is HMAC-SHA-256 over the raw request body, with a secret that itself is SHA-256 of your API token. In Node.js:
import crypto from 'node:crypto';
function verifyWebhook(rawBody, signature, apiToken) {
const secret = crypto.createHash('sha256').update(apiToken).digest();
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex'),
);
}
In Python:
import hashlib
import hmac
def verify_webhook(raw_body: bytes, signature: str, api_token: str) -> bool:
secret = hashlib.sha256(api_token.encode()).digest()
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature, expected)
Without this check, an attacker can POST a fake "status": "paid" body and fulfil an order without paying. Dozens of merchants got caught by exactly this between 2022 and 2024.
Idempotency
When delivery fails, Crypto Pay retries the webhook with growing backoff. That means the same update_id (or invoice_id) may arrive twice. Keep a processed-events table, or check order state before fulfilment:
if (await orderAlreadyFulfilled(invoiceId)) {
return res.status(200).end(); // ack, do nothing
}
await fulfillOrder(invoiceId, payloadJson);
await markAsFulfilled(invoiceId);
Step 5. Polling as a fallback
If a webhook doesn’t reach you (your server was down), don’t lose the payment — poll getInvoices every few minutes:
GET https://pay.crypt.bot/api/getInvoices?status=paid&offset=0&count=100
Crypto-Pay-API-Token: ...
Returns the array of paid invoices. Diff against your DB, fulfil the missed ones. A reasonable cron interval is every 5 minutes for active orders.
Supported assets (2026)
At time of writing Crypto Pay accepts:
| Network | Assets |
|---|---|
| TON | TON, USDT-TON, NOT, jUSDC, jUSDT, MAJOR, DOGS, HMSTR |
| Bitcoin | BTC |
| Ethereum | ETH, USDT-ERC20, USDC-ERC20 |
| Tron | USDT-TRC20 |
| BNB Chain | BNB, USDT-BEP20 |
| Solana | SOL, USDT-SPL |
| Litecoin | LTC |
The exact list moves — call getCurrencies before onboarding a merchant so your UI doesn’t expose assets that aren’t actually live.
Common errors and how to catch them
INVOICE_NOT_FOUND
Hits when you call getInvoices for an invoice_id created under a different app token. Each token only sees its own invoices.
EXPIRES_IN_INVALID
expires_in must be 1 to 2 678 400 (31 days). Higher values create the invoice without expiry but still return an error. Validate the range client-side.
Rate moves between create and pay
If you make a fiat invoice ($9.99) with accepted_assets: TON,USDT, the FX rate is locked at the moment of payment, not creation. Under volatility this creates micro-arbitrage — the buyer can wait for a favourable moment. For sensitive flows use a short expires_in (60–300 seconds).
Webhook arrives without a signature
That’s either a forgery or a very old Crypto Pay client. Reject any request that lacks crypto-pay-api-signature. All production webhooks have been signed since 2024.
App-level security
- Never put the token into frontend / client-side bot code. A Crypto Pay token equals access to the balance. Server only.
- HTTPS on the webhook endpoint. Without TLS the signature won’t help against MITM on the network path.
- Log every webhook’s
update_id— invaluable for dispute review and cron fallback. - Split environments by token. Production token stays in prod; staging gets a separate testnet token. An accidental refund from the prod balance is a bad day.
- Enable 2FA on the owner’s Telegram account — compromising the owner equals compromising the balance.
Alternatives and combinations
- xRocket Pay — direct competitor, very similar API (
createInvoice/ webhook), separate user base. Many shops integrate both: the buyer picks “Crypto Bot or xRocket”. - Telegram Stars — Telegram’s own internal currency, not crypto. Works for digital content inside Telegram, but monetisation and payout run through Telegram, not a crypto network.
- Direct on-chain via TON Connect — non-custodial, no third-party dependency, but requires your own backend and UX work. Worth it for larger merchants who already run TON infrastructure.
A reasonable production strategy: Crypto Pay + xRocket Pay covers ~90% of the Telegram audience; direct on-chain via TON Connect handles users who avoid custodial services or transact in larger amounts.
Production checklist
Before flipping the switch on prod, walk through this list:
- Token lives in
.env/ Vault, not committed to source control. - Webhook on HTTPS, signature verified via HMAC-SHA-256.
- Idempotency by
update_idorinvoice_id. - Cron fallback via
getInvoicesfor missed webhooks (every 5 minutes). - Raw-body logging of every webhook — for dispute resolution.
-
expires_inis set explicitly (short for fiat invoices, 60–300s). - Retries on
createInvoicenetwork errors with exponential backoff. - Alert on app balance dropping below threshold — so refunds aren’t held up.
- Escalation playbook with
@CryptoBot_Support. - Test scenario through testnet
@CryptoTestnetBotpassed end-to-end.
Wrapping up
Crypto Pay is the least painful way to start accepting crypto payments in a Telegram bot. From zero to the first confirmed invoice is a couple of hours of work: issue a token, write createInvoice, expose a webhook endpoint with signature verification. The custodial nature is the core trade-off — you save on infrastructure but hand reliability and regulatory exposure to Crypto Bot.
For pilots and micro-stores it’s almost always the right answer. For serious volume — pair it with direct on-chain acceptance via TON Connect, so you don’t depend on a single gateway.
Sources
- help.crypt.bot/crypto-pay-api — official API documentation, including the method list and webhook payload shapes.
- Crypto Pay testnet — separate environment for integration tests.
- @CryptoBot — the main bot for managing apps.
Frequently asked
Do I need KYC to issue a Crypto Pay token?
How is Crypto Pay different from xRocket Pay and Telegram Stars?
How do partial payments or overpayments work?
What about refunds?
Is webhook signature verification mandatory?
What fees does Crypto Pay charge merchants?
Related
- WalletsMay 9, 2026
Crypto Bot 2026: a guide to Telegram payments
How @CryptoBot works in Telegram in 2026: cheques, P2P market, invoices, Crypto Pay API, in-chat tipping.
- WalletsJan 3, 2026
Wallet in Telegram 2026: features and custodial limits
What the in-Telegram Wallet can do, where its limits are, how its custodial nature differs from Tonkeeper and when the service is safe and when it is not.
- WalletsFeb 13, 2026
Best TON wallets 2026: comparison and picks
A comparison of the main TON wallets — security, UX, jetton support, DeFi compatibility. When to pick custodial, when non-custodial