Telegram Stars (currency XTR) let you sell digital goods inside Telegram with no bank, no card network, no Stripe/PayPal, and no KYC on the selling side. The catch most tutorials skip: Telegram requires answerPreCheckoutQuery within 10 seconds of every payment attempt — there's no auto-approve fallback. That means you need something always-on, listening. Normally that's a $5-20/month VPS or a serverless function with its own billing.
I didn't want a second hosting bill for a side project, so I run the responder entirely inside a public GitHub repo's Actions minutes, which are unlimited for public repos.
The trick
A single GitHub Actions job can run up to 6 hours. So:
- Long-poll Telegram's
getUpdatesfor ~5h50m inside one job. - Commit the updated offset + a dedup list of already-fulfilled payment IDs back to the repo as the last step.
- A
cronschedule re-triggers the next run every 6 hours — it queues right behind the previous job (same concurrency group), so the gap between one run ending and the next starting is seconds, not minutes.
Net result: near-continuous coverage, $0/month, no separate hosting account to manage or lose access to.
on:
schedule:
- cron: "0 */6 * * *"
concurrency:
group: telegram-stars-bot
cancel-in-progress: false
jobs:
respond:
timeout-minutes: 355 # just under the 6h hard cap
The bug this trick hides until your first real sale
The last step commits state.json (the getUpdates offset) and fulfilled.json (processed payment IDs) back to main. A plain git push there is a trap: if anything else pushes to main during that ~5h50m window — even an unrelated commit to a different file — the push fails with a plain ! [rejected] (fetch first). The job itself still shows green in the Actions tab (the failure is buried in one sub-step), so nothing alerts you.
What actually breaks: the whole run's progress — offset and the fulfilled-payment dedup list — gets silently discarded. The next run starts from the old offset, Telegram happily replays the same updates (it doesn't drop them without a confirmed offset), and because the dedup record also reverted, a real purchase could get double-processed — not a double charge to the buyer, but a duplicate delivery message, which looks broken to a paying customer on their first interaction with your bot.
Fix: retry the push with a rebase instead of giving up after one attempt.
for i in 1 2 3 4 5; do
git push && exit 0
git fetch origin main
git rebase origin/main || { git rebase --abort; exit 1; }
sleep 3
done
I found this by noticing a run failure buried in email notifications, not from a customer complaint — it hadn't sold anything yet when I caught it, but it was a live landmine for the first real sale.
Where it stands
Live since Oct 17: dedicated bot (@freelancerkitbot), pre_checkout_query answered automatically, successful_payment triggers an automatic delivery message, replayed updates can't double-deliver (charge IDs are deduped). Zero sales so far — I'm not going to pretend otherwise — but the plumbing is solid and tested (unit tests mock the Telegram API, no token needed to run them).
Code (MIT): https://github.com/kleogeos/telegram-stars-checkout
If you want to see it live: one of the checkout pages has a "Pay with Telegram Stars" button wired to this exact repo.
Top comments (0)