"Just add payments to the bot" sounds like a one-liner. In practice, the happy path is the easy 20% — the other 80% is the edge cases that decide whether you actually get paid and keep customers. Here's what actually matters when a Telegram bot handles money, learned from shipping a few of them.
Two very different ways to charge
There are two models, and picking the wrong one causes most of the pain:
-
Telegram's native payments (
sendInvoice+ a provider token like Stripe). The whole flow stays inside Telegram, the UX is excellent, and you get a cleansuccessful_paymentupdate. Great for digital goods and simple checkouts. - External payment link / your own gateway. You generate a link, the user pays on a web page, and your backend gets a webhook. More flexible (subscriptions, local providers, complex carts) but you own more of the plumbing.
For most "sell a product or a subscription" bots, native payments are the right default. Reach for external only when the provider or business model forces it.
The pre-checkout step is not optional
With native payments, Telegram sends a pre_checkout_query and you have ~10 seconds to answer it. If you don't answerPreCheckoutQuery(ok=true) in time, the payment fails — even though the user did nothing wrong.
This is where you do final validation: is the item still in stock, is the price still valid, is the user allowed to buy. Approve only if everything checks out. Skipping real validation here is how people sell things they can't deliver.
Treat money events as untrusted until verified
Two rules that save you from fraud and angry customers:
- Verify webhook signatures. Anyone can POST to your webhook URL. If you act on an unsigned "payment succeeded" call, you'll ship goods for payments that never happened. Always verify the provider's signature before trusting the event.
-
Make payment handling idempotent. Networks retry. You will receive the same
successful_payment/ webhook twice. Store a unique payment ID and ignore duplicates, or you'll deliver (or refund) twice.
The database is the source of truth, not the chat
A Telegram message can be missed, edited, or arrive out of order. Don't drive business logic off the conversation state alone. Persist every order with an explicit status — pending → paid → fulfilled → refunded — and let the bot read from that. When a user writes "where's my order?", you answer from the database, not from memory.
Plan for refunds and failures from day one
The unglamorous parts customers judge you on:
- A clear message when a payment fails (and an easy retry), not silence.
- A defined refund path — Telegram supports refunds for native payments; wire it up before you need it.
- An admin view: who paid, what's unfulfilled, what errored. A bot that takes money but gives the owner no visibility is a support nightmare.
A sane minimum architecture
Bot (webhook mode, not long polling for production) → a small backend that validates and writes to a database → payment provider webhooks landing on a separate, signature-verified endpoint. Keep secrets in env vars, log every money event, and alert on failed payments. That's it — boring on purpose, because boring is what you want around money.
None of this is hard individually; the value is doing all of it so nothing silently breaks once real money flows. I build Telegram bots with payments, databases and admin panels as a freelancer — you can see examples at vengstudio.online. Happy to answer implementation questions in the comments.
Top comments (0)