This is a submission for the MLH x DEV Writing Challenge
What I Built
Lookout is a practice wallet where a risky payment waits until someone you trust agrees to it.
The theme at Rowdy Hacks XII was "Heist," and the title sponsor, Swivel, asked teams to build a shield against social engineering. So we flipped the theme around. Instead of planning a heist, we built the lookout who stops one.
People over 60 lost billions of dollars to scams last year, and in almost every case the victim approved the payment themselves. Banks check whether the account holder authorized a transfer. In a scam they did authorize it, because a stranger on the phone talked them into it. The fraud happens in the conversation, so a login check never sees it.
Lookout steps in right before the money moves. In our demo, Rosa opens the wallet to send $2,000 because "the IRS" texted her about a warrant. Lookout reads the payment along with the message that prompted it, scores the risk from 0 to 100, and decides to allow, verify, or hold. On a hold, Rosa sees which stage of the heist the scammer has reached, a few plain-language reasons, and hears a spoken warning in English or Spanish. Her daughter Maya, the trusted contact she picked ahead of time, gets the request on her own phone with the amount, the recipient and the signals that fired. Maya never sees passwords or codes. She taps approve or deny, and Rosa gets the answer along with a safer next step.
We kept a few rules the whole weekend. Gemini interprets the message, but a small scoring function in plain code makes the decision, and no single signal can hold a payment on its own, since words like "urgent" show up in normal texts all the time. Each point in the score maps to a sentence Rosa can read. Lookout never moves money by itself: releasing a held payment requires a human to confirm, and that check lives in the code instead of the prompt. Codes are redacted before the request reaches anyone else.
We also added a /learn page, a six-round game where you read a message, flag the scam signals and watch Lookout's score build using the real weights and thresholds. Catching a scam once helps one person on one day. Teaching the pattern sticks around longer.
The thing I'll take away from this build is that catching scams turned out to be the easier half. The harder half was leaving the rent payment and a grandkid's birthday money alone, because one false alarm costs more trust than a missed scam.
Demo
You can visit the landing page at stoptheheist.us and try the scam-signal trainer at stoptheheist.us/learn.
Lookout
A risky payment waits until someone you trust agrees. This is a practice wallet!
Main application
-
apps/webis the landing page, the wallet, and the Lookout's phone. -
apps/apireceives a payment check and returns allow, pause, or hold.
Open / for the door. Open /wallet for Rosa. Open /crew/maya-demo for Maya. On a wide screen the wallet sits in one phone. On a real phone it fills the screen.
Data
-
apps/api/schema.sqlis the Tiger tables and the hourly chart. -
apps/api/app/db.pyis the records: payments, approvals, reasons, watchers, pulse samples. -
apps/api/app/samplesis the IRS text used in the demo.
How a payment is decided
-
apps/api/app/labels.py— Gemini, or the offline rules, reads the words. -
apps/api/app/scoring.py— the fixed score. One signal cannot hold a payment. -
apps/api/app/why.py— why the money is moving, ranked by threat and updated from a finished payment or a denial. -
apps/api/app/desks— watchers. A new file…
Partner Technologies
Gemini does the reading. It takes the payment note and the suspicious message (pasted text or a screenshot) and returns structured JSON against a fixed schema with fields for an authority claim, a threat or reward, urgency, secrecy and a sensitive request. Using structured output was the decision that made everything else work, because the model can only hand back labels our scoring layer checks. It never gets to return a verdict. If Gemini errors out or the key is missing, Lookout falls back to an offline rules engine so the demo survives bad venue Wi-Fi. Gemini also writes replies for our phone voice agent, with scripted lines as a backup.
ElevenLabs speaks the warnings using eleven_multilingual_v2, with one English voice and one Spanish voice. For an older person in the middle of a scam, hearing "You are being rushed. That is how scams work." out loud gets through better than a red banner on the screen. San Antonio is a bilingual city, so Spanish had to be there from the start.
Tiger Data (TimescaleDB) stores payment attempts, risk checks and vitals samples as hypertables. A continuous aggregate rolls the risk checks into an hourly chart for Maya's history view. When no Tiger URL is configured, the API drops to SQLite on a Docker volume, which got us through setup more than once.
We also wired up Presage SmartSpectra to read pulse and breathing from the camera. A spike only counts after a 15-second baseline, and if the sensor isn't there the code reports nothing instead of making up a number. Elevated pressure adds one signal to the score and nothing more.
The rest of the stack is FastAPI and Next.js, Twilio for a call-in voice check, a shared list of reported scam numbers, an ESP32 alarm light that polls for active holds, and Docker Compose behind Caddy with automatic HTTPS.
Hackathon Experience
Rowdy Hacks XII was at UTSA, my home school, and I built Lookout with my teammate Takshi Savaliya. For 24 hours the room was full of vaults, escape rooms and getaway cars.
Around 3am the first thing to break was our plan. We had a design doc that mapped out a dozen prize tracks, and by the middle of the night it was clear we couldn't ship all of it. We cut hard and protected the core flow of wallet, risk check, hold screen and trusted-contact approval. Everything else had to earn its way back in.
Then deployment broke right before judging. Our config found the repo root with parents[3], which worked fine on our laptops and overflowed inside the container at /app, so uvicorn crashed on startup. A type check in the scroll intro also failed the production build. Both fixes were one line each and went in during the last stretch before submission. Twilio's trial account only calls verified numbers, too, so for the demo the "call my phone" button turned into "call in."
What I'll remember most is that many of the judges were volunteers without a technical background. That pushed us to make Lookout make sense in 30 seconds: "You're grandma. You just got this text. Try to send the money." When someone who doesn't write code can feel it work in their own hands, I know the engineering underneath is doing its job.
Top comments (5)
Would love any comments!
Very cool !!
Thank you!!
This is so cool! We should totally find a time and talk more about this.
Let's do it!