tgpay cryptoAPI
crypto-payinvoicesapiwebhook

Accepting payments with invoices

3 min readUpdated Oct 9, 2026

An invoice is how you charge a Telegram user. You create one through the API, send the payer its link, and a successful payment credits your app balance minus the merchant fee.

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 invoice 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 when that option is available. Automatic payment requires enough credited balance before the payment deadline and no outgoing-payment restriction. A deposit alone is not proof of invoice payment.
  4. You’re notified. If you configured a webhook URL, an invoice_paid event is queued after payment. Your app receives the amount minus the fee.
  5. Fulfill the order. First verify the webhook signature, match the invoice to your order and confirm its status is paid. You can also check the status with getInvoices; no further blockchain confirmations are needed.

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 when conversion is available. If it cannot run, the payment still succeeds in the original asset; check is_swapped and the swapped_* fields.

Useful invoice options

  • description — shown to the payer on the Pay invoice 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.

Description, hidden content and the after-payment button may be hidden from payers until the content is approved. Do not rely on them being visible immediately after you create the invoice.

An unpaid invoice can be canceled with deleteInvoice.

Refunds

refundInvoice returns the crypto actually paid — 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 crypto amount originally paid; the invoice tracks them in refunded_amount. For fiat invoices, refunds use paid_asset and paid_amount, not the fiat face value. Your app must hold enough of the original payment asset, even after an automatic swap. 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 TgPayCrypto-API-Signature header — HMAC-SHA256 over the raw request body, keyed by the SHA-256 of your main API token — before you treat a payment as real, and dedupe on update_id so a retry doesn’t ship the order twice.