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. A payer without enough balance can fund the invoice from an external wallet; it settles automatically when their transfer arrives, and to you it looks the same.
- You’re notified. The
invoice_paidwebhook fires, and the amount lands in your app balance. - Fulfill 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 catalog is denominated in a real-world currency.
If you’d rather quote a firm price, rate_lock_seconds freezes the conversion
rates at creation for a bounded window — the payer sees exactly the locked
amounts, and you take the rate risk for those minutes. Details in the
invoices reference.
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.
- open_amount — no fixed amount; the payer enters one at pay time. The natural shape for donations and tips.
An unpaid invoice can be canceled with deleteInvoice.
Refunds
refundInvoice returns a paid invoice’s face amount — or any part of it —
from your app balance to whoever paid it, including anonymous payers, without
revealing who they were. Partial refunds accumulate up to the face amount; the
invoice tracks them in refunded_amount. Pass a spend_id so a timed-out
retry replays instead of refunding twice. The platform fee is not refunded.
⚠️ Verify the webhook signature before you fulfill
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.