Building a Real-Time Ticket Listings Monitor in Python: From Legacy .NET to a Lean Alerting Pipeline
How I ported a ticket-inventory monitoring workflow to Python, solved a proof-of-work bot challenge in pure code, pushed the heavy lifting into SQL Server, and shipped a tool that alerts the team on Slack within seconds of a new listing appearing.
The problem
In the secondary ticket market, timing is everything. A well-priced listing for a high-demand show can appear and disappear in under a minute. Our purchasing team needed to know, immediately, when a new listing showed up for the events they were tracking on GoTickets, filtered by price ceiling and section, and with a one-click link to buy.
We already had this workflow for another marketplace, running inside a larger C#/.NET application. But that app was heavy, tightly coupled to a desktop environment, and hard to deploy on a fresh machine. For GoTickets, I wanted something different: a small, dependency-light Python service that anyone on the team could stand up in ten minutes and leave running unattended.
That project became PyAppPullTickets. This post walks through what I built, the technical decisions behind it, and what I learned.
What the tool does
Every 60 seconds the monitor:
- Reads a plain-text watchlist of event URLs, each with optional rules like
maxprice:2000,include:Floor;Level 1,exclude:Level 3. - Fetches the live listings for each event in parallel.
- Hands the raw JSON to SQL Server, which decides what is new versus already seen.
- Tags the cheapest single seat and cheapest group, and how far below the next price they sit.
- Posts a compact table to Slack with a BUY link per row, and @-mentions the buyer when a listing is a standout bargain.
The first scan of an event is treated as a baseline and produces no alerts. From the second scan on, only listings the database has never seen before are reported. That single rule eliminated almost all of the noise the team had been dealing with.
2026-09-07 15:30:56 INFO DB cleanup done
2026-09-07 15:30:56 INFO Scan 1 event(s)
2026-09-07 15:30:58 INFO PoW solved: number=634621 took=538ms
2026-09-07 15:30:58 INFO [1638934] 12 listings
2026-09-07 15:31:00 INFO Sleep 60 second(s)
Architecture
I kept the codebase deliberately small. The whole application is about 650 lines across five files:
| File | Responsibility |
|---|---|
appGoTickets.py | CLI entry point, HTTP client, challenge solver, monitor loop |
db.py | Thin pyodbc layer that calls stored procedures |
slack.py | Builds and sends the alert table via incoming webhook |
config.py | Connection string, webhook, interval, thread count, thresholds |
sql/GoTickets.sql | Tables, indexes, and the two stored procedures |
The design principle was "Python fetches, SQL decides, Slack tells." Each layer has one job, which made the whole thing easy to test in isolation and easy to explain to a teammate.
Multiple run modes from one entry point
Rather than build a separate debugging tool, I gave the script several modes:
Run.bat monitor cleanup DB, then every 60s scan all links, save to DB, Slack new listings
Run.bat once one pass, print only (no DB / Slack)
Run.bat "<event url>" one link, print only
Run.bat dbtest check SQL connection
Run.bat cleanup truncate the data and alert tables
Run.bat deploysql create tables and procs from sql/GoTickets.sql
The print-only modes save the raw API response to disk. That turned out to be invaluable: whenever the upstream API changed shape, I had real payloads on hand to test the SQL parsing against.
The three hard parts
1. Getting listings without a browser
The listings endpoint is guarded by a proof-of-work challenge that the site's own JavaScript normally solves. Rather than ship a headless browser on a machine that had to run 24/7, I studied the protocol and implemented the handshake directly in Python with the standard library. The solve takes a few hundred milliseconds, which is more than fast enough for a 60-second cadence, and the deployment has zero browser dependencies.
2. Detecting "new" listings reliably
Instead of diffing JSON in Python, the raw response goes straight into SQL Server and a stored procedure does the work with OPENJSON and set-based joins against the history table. The first scan of an event is a silent baseline; every later scan reports only listings never seen before, already filtered by price ceiling and section rules. Everything runs in a transaction, so a bad payload can never leave the tables half-updated.
The same procedure uses window functions to tag the cheapest single seat and cheapest group, together with the price gap to the next listing. When that gap is large, the Slack message @-mentions the lead buyer. Listings with limited or obstructed views get an (*OV*) marker so nobody buys a bad seat by accident.
3. Deploying to a machine I had never touched
The .NET predecessor was painful to install, so I wrote the README as a checklist a non-developer can follow: prerequisites, the three files to edit, a three-step verification sequence, Task Scheduler settings for unattended startup, and a troubleshooting table for the most common errors. The SQL deployment script is idempotent, and the whole tool depends on just two third-party packages.
Resilience details
Small things that keep a long-running monitor healthy:
- Thread pool per scan. Events are fetched concurrently with a
ThreadPoolExecutor, sized from config. One slow event never delays the others. - Failure isolation. Each event is wrapped in its own try/except. A 403 on one URL is logged and the loop moves on; the process never dies.
- Rotating outbound proxies. Proxy credentials are loaded from a text file and chosen per request. Credentials are masked in the logs.
- Batched Slack delivery. Alerts are sent in groups of 20 rows to stay well inside Slack's message size limits.
- Dual logging. Console and a log file, so the operator can see status live and I can diagnose issues after the fact.
- Decimal-safe prices. Money values cross the Python/SQL boundary as
Decimal, neverfloat, to avoid rounding surprises on the buy link.
What I would do differently
- Async I/O. For a handful of events, threads are fine. At a few hundred events,
asynciowithhttpxwould use far less memory. - Structured logging. JSON log lines would make it trivial to ship into a dashboard.
- Health endpoint. A tiny HTTP heartbeat would let a monitoring service confirm the loop is still alive rather than inferring it from Slack silence.
Skills this project exercised
- Python for production tooling:
requests,hashlib,concurrent.futures,logging, clean CLI design. - Reverse-engineering a client protocol from network traces and implementing it without a browser.
- SQL Server:
OPENJSON, window functions (LAG,ROW_NUMBER),STRING_SPLIT, transactional stored procedures, idempotent deployment scripts. - Integration: Slack incoming webhooks with Block Kit markdown,
pyodbcagainst AWS RDS. - Porting and simplification: taking a workflow out of a large C#/.NET application and re-expressing it in a fraction of the code while preserving behaviour the business relied on.
- Operational thinking: deployment docs, failure isolation, unattended running, troubleshooting guides written for the people who will actually use them.
Closing
The most satisfying part of this project was not any single piece of code. It was watching the first real alert land in Slack, with a section, a price, a BUY link, and a "Cheapest single by $45" tag, less than two seconds after the listing appeared on the site. The buyer clicked, and the ticket was ours.
Small tools, well-scoped and well-documented, can carry a lot of business weight. This one runs quietly on a Windows box in the corner, and the team checks Slack instead of refreshing a web page all day.
If you are working on marketplace monitoring, event-driven alerting, or moving legacy .NET workflows to Python, I would be glad to compare notes.
Comments
Post a Comment