All posts
AWSServerlessPythonSide Project

How a Waitlisted Train Ticket Turned Into a Cloud Hack

IRCTC goes silent on waitlisted tickets booked in advance, so I built a serverless PNR notifier on AWS Lambda. Captchas and cloud-IP blocking killed two other approaches first.

A waitlisted ticket

It began with a booking that should have been routine. I logged into IRCTC, filled in my details for the Duronto Express, and hit "Book Now". When the screen refreshed: WL, waitlisted.

At first I wasn't worried. Surely IRCTC would keep me posted. In 2025, how hard can it be to send an SMS when a waitlist number changes? Harder than I assumed. IRCTC alerts are sparse: a booking confirmation, maybe a cancellation, and a message when the reservation chart is prepared. That is the whole list. The "Get SMS" option works only within three days of the journey, so for tickets booked weeks or months ahead the system stays silent.

So the money was deducted, the journey was uncertain, and I had no way of telling whether anything was moving in my favour. The only option was to refresh the website repeatedly, or fire off curl commands to see the same line of text come back.

Every traveller with a waitlisted ticket knows the questions: wait, cancel, or book a backup? One timely update answers all three. The official system provides none.

Building it myself

Once I accepted that IRCTC wasn't going to help, the obvious move was to build the alerting myself.

What I needed was small: check the PNR at intervals, remember the last result, and ping me when something changed. That is an ordinary backend problem in unfamiliar surroundings.

I also had Copilot, which meant the boilerplate was no longer the expensive part. Something that would once have taken half a day took me just over an hour, because I could spend the time on the logic and let the assistant produce the scaffolding.

Three attempts at a data source

Attempt 1, IRCTC itself: captchas and session walls

The source of truth should have been the obvious place to start. IRCTC wraps its APIs in session state and captcha verification, so even a single request meant juggling cookies, headers and a captcha image. The API is anti-automation by default. The curl alone is a tangle of cookies and random tokens:

curl 'https://indianrail.gov.in/enquiry/CommonCaptcha?inputCaptcha=88&inputPnrNo=4224374267&inputPage=PNR&language=en2%2C' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...' \
  -b 'JSESSIONID=...; f5avraaaaaaaaaaaaaaaa_session_=...'

Unless I wanted to solve captchas with OCR, which I didn't, this route was a dead end.

Attempt 2, Redbus: works locally, dies in the cloud

Redbus returned exactly the data structure I wanted, a JSON payload with pnrNo, passengers and currentStatus. From my Mac it was smooth and predictable:

curl --location 'https://www.redbus.in/rails/api/getPnrToolKitData' \
  --header 'Content-Type: application/json' \
  --data '{"mobile": "", "pnr": "4224374267"}'

Then I deployed the identical request on AWS Lambda and it broke. Calls that worked locally failed in the cloud, because Redbus applies IP range filtering to block traffic from known cloud providers. My Lambda's IP was blacklisted before the request was read.

Attempt 3, ConfirmTkt: the one that worked

The ConfirmTkt endpoint is a POST with a minimal payload, and it returns passengerStatus, currentStatus and a prediction percentage.

curl --location 'https://cttrainsapi.confirmtkt.com/api/v2/ctpro/mweb/4224374267?querysource=ct-mweb&locale=en&getHighChanceText=true&livePnr=true' \
  --header 'Content-Type: application/json' \
  --data '{"proPlanName": "CP7","emailId": "","tempToken": ""}'

No captcha, no session, no IP filtering. Lambda executed successfully and I had reliable data.

Each of the three attempts failed to a different defence: captchas, then cloud-IP filtering, then nothing at all.

The design

Poll, diff, notify. I kept it minimal to avoid operational overhead.

The architecture

At the centre is an AWS Lambda on Python 3.11. It POSTs to the ConfirmTkt endpoint, parses out passengerStatus[] (notably currentStatus and prediction), then compares that snapshot to the previous one.

Remembering the previous snapshot did not need a database. AWS Systems Manager Parameter Store holds small state well, so a compact JSON sits at a key like /pnr/<PNR>/last_statuses. Reads and writes are fast, cheap, and need no extra infrastructure.

EventBridge Scheduler handles the cron. I set cron(0 * * * ? *) with timezone = Asia/Kolkata, so it runs at the top of every hour IST. Scheduler can pass target input, so tracking multiple PNRs is a matter of creating multiple schedules with different JSON inputs (for example {"PNR":"4224374267"}). Closer to the journey date, either add a second schedule with rate(10 minutes) or update the existing one.

What happens on each run

  1. Fetch the latest PNR JSON via urllib with a small payload ({"proPlanName":"CP7",...}) and a sensible timeout.
  2. Extract statuses per passenger into a dict:
    { "1": { "status": "CNF", "prediction": "..." }, "2": { "...": "..." } }
    
  3. Diff against SSM's previous snapshot to detect meaningful changes: WL→CNF, WL position shifts, prediction deltas.
  4. Format a crisp message (train, DOJ, class, each passenger line) so it's readable at a glance on a phone.
  5. Publish to Amazon SNS using a topic you own, subscribing over SMS, email, or both. For testing, a TEST_SMS=1 env flag forces a snapshot every run; set it back to 0 for only-on-change behaviour.

Configuration is entirely environment variables: PNR, SNS_TOPIC_ARN, SSM_PARAM_NAME (optional), HTTP_TIMEOUT, and the logging knobs. No extra libraries, just urllib and boto3, so the deployment artifact stays a single-file zip.

Small, boring, reliable. Exactly what this problem needed.

What it costs to run

I did wonder whether the cloud bill would make this pointless. It doesn't. The system is very nearly free.

ServiceUsageCost
AWS Lambda~4,300 invocations/month (hourly)Free tier (1M/month)
EventBridge Scheduler1 to 2 schedulesFree tier
SNS, emailPer notification₹0
SNS, SMS (India)Per notification~₹1 to ₹1.5
TotalPer journey~₹2 to ₹3

Less than a cup of tea at the station.

What I took from it

The captchas, session walls and IP blocking I hit are not technical quirks. They are deliberate defences, and an official API existing is not the same as an official API being usable.

The fix needed no database, no container and no pipeline. Three lightweight AWS services covered it, and the deployment artifact is one zipped file.

The code is on GitHub as IRCTC-pnr-status-notifier.

Leave a comment

No account needed. Leave the name blank and you'll get a random one.

0/2000
By email

Get the next one in your inbox.

Engineering post-mortems, mathematics, and short fiction, in English and Bengali. Infrequent by design: an email only when there is something new worth reading, never on a schedule. One click to leave, any time.

Double opt-in: you'll get one email to confirm, and nothing else until you do.