← All rooms

Commission 01 · Visa

Visa Tribuna

WhatsApp has no room where a crowd can argue a referee's call, so I built the room out of thousands of private conversations and one operator's screen.

World Cup 2026 · visatribuna.club · dashboard.visatribuna.club

  • 15days from the phone call to the opening match
  • 24days from empty repository to live domains
  • 90svoting window while the referee reviews the call
  • 232tests keeping the scoring and voting paths correct

A kid with a Panini album

Every four years the Panini album comes back. Same ritual on every continent: you buy the packets at the corner shop, you trade your repetidas with whoever is outside, and somewhere along the way you end up knowing the squads of teams from the other side of the world. I did this as a kid. I was very serious about it.

So when the phone rang and the question was whether I could build a fan experience for the World Cup, I did not need time to think about it. I have loved this tournament my whole life. Somebody was offering to let me build something inside it.

Then I looked at the calendar.

Fifteen days

The call came on the twenty seventh of May. The opening match was on the eleventh of June. The actual requirements did not arrive until the third of June, which left eight days. There was nothing yet. No designs, no architecture, no color palette, no dashboard, no database, no idea of what the product actually was beyond a sentence describing a wish.

Then a second one arrived. Rexona, for a different agency, on its own schedule, with a completely different game. For the stretch where the two overlapped I was building both at once.

A sponsor activation of this kind normally runs two to four months from brief to launch, with a team on it: a creative director, a designer, a backend engineer, someone on infrastructure, a project manager, and a lawyer somewhere in the background.

I did both projects, and I did them by myself. That meant the user experience, the interface, the type, the system architecture, the database design, the operator dashboard, the deployment, the monitoring, and the legal research. All of it, twice, in parallel.

That constraint explains every decision that follows. With two weeks and no team, there was no room to be wrong twice about the same thing. Everything below is a choice made under it.

What WhatsApp cannot do

First, where this happened, because it shapes everything. Both activations lived at Tribuna Club, a ticketed fan event built in the north car park of Estadio El Campín in Bogotá, running from the twenty seventh of June to the nineteenth of July. Doors at midday, matches on more than two hundred square metres of screen, a few thousand people inside at any one time. You bought a ticket, you came in, and the sponsor stands lined the floor. Visa had one. Rexona had another. That is the room these were built for: not a broadcast and not a national campaign, but a specific floor full of people who had paid to be there.

The request was reasonable and, as described, impossible.

Connect every fan to one bot. Run one big interactive conversation. Let the operator watch all of it from a single screen. A fan scans a code at the Visa stand, the referee walks over to the pitchside monitor, and the whole floor argues about the penalty in one place.

That product does not exist on WhatsApp, and no amount of effort would have made it exist. There is no way to broadcast to a large group and collect everyone's replies into one shared room. Group chats put every member in view of every other member, which is already wrong for a brand activation, and the official group tooling caps out at eight people anyway. Broadcast lists look like the answer until you read closely: they are a feature of the consumer phone app, not of the business platform. A business can certainly send in bulk, but sending in bulk is not a room. It is a stack of private messages, which is precisely the problem.

So the first thing I delivered was not code. It was a document telling the client that the thing they had described could not be built, on the record, before anyone found that out the hard way.

The second thing I delivered was the version that works.

The room is on the server

The reframe is one sentence. The shared room does not exist on the fan's phone. It exists on the server.

Every fan who scans the code gets their own private one to one conversation with the business number. That is the only shape WhatsApp actually supports, so I stopped fighting it. When the referee goes to the pitchside monitor, the operator presses one button and the question goes out to every one of those conversations at once. Every reply comes back to one place, gets recorded, and is gathered into a single live view.

The crowd was never inside WhatsApp. The crowd is something the server assembles.

That pivot is not a workaround, it is the foundation. A leaderboard needs one authority keeping one clock. Fair timing needs a measuring point the fan cannot reach. Deciding who won needs a single source of truth about who was right and who was fastest. A real group chat gives you none of that. Thousands of private conversations plus a server gives you all of it.

There was a second reason WhatsApp was right, and it was not technical. In Colombia, WhatsApp is on very nearly every phone that goes online. There is nothing to download, no app store, no sign up, no password. The fan is already in the app where they were going to argue about the penalty anyway.

Three buttons and twenty characters

WhatsApp has no poll. Polls are a feature of the consumer app, and a business account cannot send one.

So every review goes out as tappable reply buttons, and the ceiling is unforgiving: three options, twenty characters each. "Was it a penalty?" with three buttons underneath is the entire interface. Every bit of nuance in the original brief had to survive being compressed into sixty characters of button text.

The counting happens on the server, and this is where the crowd size stops being a nice number and starts being an engineering problem. The event was planned for tens of thousands of visitors across its run, and the traffic from a live match does not arrive politely. Nothing happens for ten minutes, then the referee draws a rectangle in the air and everybody answers at once. I had no way to know the exact shape of that spike in advance, so I planned for the worst version of it rather than the average one.

A database will happily take a steady stream of writes to the same place. It will not take thousands of them in the same second. So the tally is not one counter, it is a hundred of them, spread out so no single one gets crushed, and added back together when the vote closes.

The window is ninety seconds. That number came from looking it up rather than from taste: at the 2018 World Cup, on-field reviews averaged a little over eighty seconds. There is no rule requiring a referee to finish inside any particular time, so ninety is a product decision, not a standard. It gives the referee room, and the operator can extend it live when a review drags on.

The clock decides who wins

Ranking is correct answers first, then fastest average response. With real prizes attached, that means speed decides who wins.

Which turns the timestamp into an attack surface. The obvious way to build it is to trust the timing that arrives with the message, and the obvious way is exploitable: anybody who worked out what was happening could claim they answered in zero milliseconds and win every tiebreak forever.

So the clock lives on the server. The response time is measured when the answer arrives at my code, not when the phone says it was sent. Network delay between WhatsApp and the server applies to more or less everyone equally, so nobody wins or loses on jitter, and nobody wins by lying.

The same instinct runs through the rest of it. Every incoming message has to prove it genuinely came from WhatsApp before anything acts on it, because a public address that writes to a leaderboard with real prizes attached will get probed. Each fan gets exactly one vote per question, and that is guaranteed by how the record is stored rather than by a check that could be bypassed. Correctness is not scored when the vote arrives, because at that moment nobody knows what the referee decided yet. The operator marks the official call when the review ends, and the scores settle then.

I built the whole thing test first. With two weeks and no colleague to check my work, tests were the safety net. There are 232 of them, and a disproportionate number sit in the scoring and voting paths, because that is where being wrong is least forgivable.

One person, watching a football match

The operator is not an engineer. On match day the operator is a person watching football on one screen with a browser open on another, and their attention belongs to the match, as it should.

So the console is a single vertical rail, and only the next thing to do is lit. Not a dashboard of everything at once, not a grid of equally shouty buttons. One glowing step, which is the step you are on. Everything else waits quietly for its turn.

Two details earned their complexity. The match name fills itself in from the live fixture list, so the operator never types a match name and can never mistype one. And the final step, announcing the winners, turns gold and pulses the instant the match ends, because that is exactly the moment a human being forgets it. The whistle goes, the room empties, attention collapses, and the winners never find out they won.

The unglamorous half

Nobody puts this part in a portfolio, so here it is.

A prize promotion in Colombia is a regulated activity. Where chance is involved it requires authorization from the state gambling regulator before it can run, and the regulator has fifteen business days to decide. Fifteen business days is three calendar weeks. Set that next to the fifteen calendar days I had: the compliance path is longer than the build, and no amount of engineering makes it shorter.

The terms and conditions are not marketing copy either. They are part of the filing. They go to the regulator with the application, before launch, which means writing them is not a task you tack on at the end. It is a deliverable with a deadline earlier than the code.

Then there is data protection. Colombia's Habeas Data regime governs what you may collect, what you must tell people you are collecting it for, and what counts as consent. Reading it changed the interface: I separated the data the promotion genuinely needs from the data a brand would merely like to have, and asked for them separately, so that entering was never quietly bundled with agreeing to be marketed to.

I did that research myself, and it changed the mechanic. A draw would have dropped a fifteen business day regulator clock into a build measured in days. Ranking on correctness and speed makes it a contest of skill rather than chance, which sits outside that regime.

Everything downstream follows from it. Phone numbers are masked before they appear anywhere. Public names are reduced to a first name and an initial. The database refuses every read by default and opens exactly one path to the world. Not because a checklist said so, but because I had read what the law actually asked for.

How one person ran two of these

The honest answer is that I did not do it alone in the way that sentence usually means.

I use a system I built called LLM Orchestrator: a team of specialized AI agents with a controller routing work between them. An architect, an implementer, two separate reviewers, a debugger, an explorer, and a researcher that checks external APIs against current documentation before anything gets specified. Every change gets reviewed twice by fresh agents that did not write it. Nothing gets called finished without the output of the command that proves it.

I also set up agents to watch the system on a schedule, checking health and reporting anomalies, and wired them so I could reach them from my phone. Which meant I could be out to dinner, get a message that something looked wrong, ask what happened, understand it, and fix it from the table.

For a couple of weeks I got to answer my system from a restaurant table like Tony Stark, and I enjoyed every minute of it.

What it came to

Twenty four days from empty repository to live domains, with the tournament already under way by day four. Eighty seven commits. A WhatsApp bot, a public fan site, an operator console, a live football data pipeline, and a transactional email system. It went live at visatribuna.club, with the operator console behind it. The tournament has since finished, so both now stand with sample data in place of real fans, which keeps the mechanics on display without exposing anybody's information.

There was a second experience inside the same system that I have not mentioned. Fans told the story of their most impulsive World Cup purchase, either by typing it or by recording a voice note straight from the browser, and a daily review picked the winners and emailed everyone else who had entered.

The part that stayed with me is not the throughput or the test count. It is that the whole project turned on refusing the brief. The client described a room. WhatsApp does not have rooms. Everything good here, the leaderboard, the timing that cannot be faked, the operator who never types a match name, exists because the impossible version got named out loud in week one instead of being half built and quietly abandoned in week three.

And somewhere in the world, a kid was filling in a Panini album while I built it.