Accepting payments with invoices
An invoice is how you charge a Telegram user. You create one through the API, send the payer its link, and your app balance is credited the instant they confirm.
The flow
- Create the invoice with
createInvoice, giving an asset and amount (or a fiat price — see below). - Send the payer the link from the response. Opening it takes them to the Pay screen in the app.
- They confirm and pay from their balance — instantly, with no network fee.
- You’re notified. The
invoice_paidwebhook fires, and the amount lands in your app balance. - Fulfil the order. Don’t wait for anything else; the payment is final at that point.
If you’d rather poll than take a webhook, getInvoices returns your invoices
with their current status. The webhook is the faster path — polling is the
fallback.
Pricing in fiat
An invoice can be priced in crypto, or in a fiat currency with a list of accepted assets. The payer then settles in whichever accepted asset they hold, converted at the rate at payment time. That’s the usual choice for a shop whose catalogue is denominated in a real-world currency.
You can also set swap_to so incoming payments are converted into a single
asset as they arrive — useful for keeping your balance in a stablecoin without
running the swaps yourself.
Useful invoice options
- description — shown to the payer on the Pay screen.
- hidden_message — revealed to the payer only after they pay. This is how you deliver a code, a key or a link without a separate delivery channel.
- payload — your own opaque string, echoed back on the webhook. Put your order ID here.
- expires_in — a time limit, after which the invoice can no longer be paid.
- paid_btn_name / paid_btn_url — the button the payer sees after paying, to send them back to your bot, channel or item page.
An unpaid invoice can be withdrawn with deleteInvoice.
Refunds
refundInvoice returns a paid invoice’s full face amount from your app
balance to whoever paid it — including anonymous payers, without revealing who
they were. It’s full-amount only and can be done once per invoice; the platform
fee stays charged.
⚠️ Verify the webhook signature before you fulfil
Anyone can POST to your webhook URL. Check the TgCryptoPay-API-Signature
header — HMAC-SHA256 over the raw request body, keyed by the SHA-256 of your
API token — before you treat a payment as real, and dedupe on update_id so a
retry doesn’t ship the order twice.
Was this article helpful?
Thanks for the feedback.