tgpay cryptoAPI
crypto-payinvoicesapiwebhook

Accepting payments with invoices

3 min readUpdated Aug 11, 2026

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

  1. Create the invoice with createInvoice, giving an asset and amount (or a fiat price — see below).
  2. Send the payer the link from the response. Opening it takes them to the Pay screen in the app.
  3. 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.
  4. You’re notified. The invoice_paid webhook fires, and the amount lands in your app balance.
  5. 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.