Skip to main content
Photo from unsplash: uzrail-bot.png

UzRail Bot — live train ticket alerts for Uzbekistan

Telegram bot + Svelte mini app that watches eticket.railway.uz and pings me the moment matching tickets become available.

––– views
- -

Personal Project


The problem

Popular Uzbek trains — Afrosiyob, Sharq, Nasaf — sell out within seconds when the railway releases new dates on eticket.railway.uz. Refreshing the booking page manually is hopeless. I built a bot that does the refreshing for me, and pings me on Telegram the instant a seat matching my filter (route, date, brand, class) becomes available.

Tech Stack Used

What it does

  1. Telegram mini app UI — a Svelte 5 web app served inside Telegram. Set up filters with a horizontal day-scroller, bottom-sheet station picker, and brand/class chips that respect the user's native Telegram theme (dark/light auto).
  2. Real-time monitoring — a background worker polls the railway's internal JSON API every ~8 seconds, deduplicating filters by (route, date) so 100 watchers on the same route still cost one upstream call.
  3. Sub-10-second notification latency — from a seat appearing to my phone buzzing, end-to-end.
  4. Production deploy on Oracle Cloud Free Tier — Caddy with auto-TLS via Let's Encrypt, systemd unit, cron-driven git auto-deploy that turns git push → live in under 60 seconds.

How it works (under the hood)

The site has no public API. The Angular frontend talks to internal JSON endpoints — I reverse-engineered the relevant ones by reading the bundle:

  1. GET /api/v1/csrf-tokenSet-Cookie: XSRF-TOKEN=<uuid>
  2. POST /api/v3/handbook/trains/list with X-XSRF-TOKEN: <uuid> and a body like
    { "directions": { "forward": {
      "date": "YYYY-MM-DD",
      "depStationCode": "2900000",
      "arvStationCode": "2900700"
    }}}
  3. The response carries trains, cars, tariffs and freeSeats — exactly enough to evaluate each user filter and trigger notifications.

The token rotates per request, so the client refreshes on 403 and retries once. Empirically the API is fine with ~5–10 s polling per unique (route, date) group; the worker also handles 429 with a 5 s back-off.

Mini app

The mini app is a standalone Svelte 5 SPA built with Vite — 17 KB gzipped JS + 2.8 KB CSS. It uses Telegram's WebApp SDK for native theme variables, MainButton, BackButton, and haptic feedback, so it feels like a first-party Telegram screen rather than an embedded webview.

Authentication uses Telegram's initData HMAC: every API request carries the signed payload as a header, the server verifies it against the bot token, and only allowlisted user IDs can read or mutate filters.

Deploy & ops

Production runs on a single Oracle Cloud Always-Free VM. The whole pipeline is a one-shot bash script — Node 22, pnpm, Caddy with auto-TLS, ufw, systemd unit. A 30-line cron script polls origin/main every minute and rolls forward when a new commit lands. Logs to /var/log/uzrail-deploy.log.

Try it

The bot is private to specific Telegram IDs (the railway API has informal rate limits per IP, so a wide-open bot would degrade). The repo is open-source — clone it, point it at your own Telegram bot token, and you have your own watcher in five minutes.