Kalshi in Utah: when your code becomes a criminal charge
Kalshi's two founders were charged with a serious crime in Utah. The reason is a prediction app that worked normally in the rest of the United States. Kalshi is the platform where you buy "yes" or "no" contracts on real events: elections, interest rates, game results. Utah looked at that and saw gambling. And Utah bans every form of gambling, lottery included. Then the story got weird: the Trump administration sued the state of Utah to protect Kalshi, according to Ars Technica.
It sounds like lawyer talk. It isn't. There's a very practical lesson here for anyone writing software that serves people in more than one place.
What happened to Kalshi in Utah?
The summary fits in four sentences.
Kalshi is regulated at the federal level by the CFTC, the US agency that oversees derivatives and futures markets. For the company, a contract on "will team X win on Sunday?" is a financial instrument, not a bet. Several states disagree, especially when the contract is about sports. Utah went the furthest: instead of sending a letter or filing a civil suit, it brought criminal charges against the company and its founders, Tarek Mansour and Luana Lopes Lara.
The federal government responded by suing the state. The argument is preemption: if federal law regulates these markets, a state can't criminalize the same product on its own.
Who wins this fight will be decided in court, probably at more than one level. I won't pretend to know the outcome. But for people who code, the outcome matters less than the question the case raises.
Why should a dev care about this?
Because almost every digital product today runs in several jurisdictions at once, and nobody on the team thinks about it until it's too late.
You deploy once and the app shows up worldwide. The law doesn't work that way. Every state, every country, sometimes every city has its own rule. What's legal in New York can be a crime in Salt Lake City. What's fine in Brazil may need a license in Portugal.
We treat this as a legal team problem. But whether a Utah user sees the buy button or not is decided by an if in your code. Does that if exist? Is it right? Who tested it?
The law has borders. The deploy doesn't. The space in between is your code's responsibility.
And there's a scary detail: the charges were against people, not just the company. Founders usually understand the risk when they sign. The dev who implemented the feature usually never even knew there was a risk.
What "federally regulated" doesn't guarantee you
This is the point I find most useful for anyone starting a product.
Kalshi isn't a startup that ignored the law. It spent years fighting for CFTC approval. It has the license. Even so, a state decided the license doesn't apply on its territory.
Translating to our world:
- Having an approved Stripe account doesn't mean your product is legal in every country Stripe serves.
- Being on the App Store doesn't mean your app complies with the law everywhere it can be downloaded.
- Following Brazil's LGPD doesn't cover you in California, and the reverse is also true.
A license is necessary. But it isn't enough. It's the start of the conversation. If your product deals with money, health, gambling, adult content, crypto or sensitive data, assume that someone, somewhere, will disagree with you.
How this shows up in code: real geoblocking
In practice, companies like Kalshi block or limit features by region. If you ever need to do something similar, the lazy and wrong way is hiding the button on the frontend. That blocks nothing. Anyone with DevTools open can call the API directly.
The block has to live on the server, at the point where the action happens. A minimal example with Bun:
// regions where the feature can't exist
const BLOCKED = new Set(["US-UT", "US-NV"]);
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
// the CDN injects the region; the header name depends on the provider
const region = req.headers.get("x-geo-region") ?? "UNKNOWN";
if (url.pathname.startsWith("/api/orders")) {
if (region === "UNKNOWN" || BLOCKED.has(region)) {
return Response.json(
{ error: "Unavailable in your region" },
{ status: 451 },
);
}
}
return new Response("ok");
},
});
Three things to notice in this snippet.
First, the 451 Unavailable For Legal Reasons status. It really exists, it comes from RFC 7725, and almost nobody uses it. Use it. It makes clear in the logs why the request failed, which helps a lot when someone from legal asks "how many people from Utah tried to buy?".
Second, unknown regions are blocked. That's the safe default. If the header didn't arrive, you don't know where the user is, and "I don't know" can't turn into "go ahead".
Third, IP alone is weak. A VPN gets around it in two clicks. For a truly regulated product, location is combined with a verified address, ID documents and sometimes the phone's GPS. The code above is the floor, not the ceiling.
The region list isn't config, it's a business decision
Here's my strong opinion: this list of blocked regions can't stay buried in a constant only the dev knows about.
I've seen this happen on a client project. A "don't sell to that state" rule lived in a config file nobody had opened in two years. The law changed, legal told sales, sales told nobody. The app kept selling.
What I would do today:
- The list lives somewhere versioned and owned, whether that's a repo, a database or a feature flag with history.
- Every change to the list has someone responsible who isn't just the dev.
- There's a log of who was blocked and when, because that log is your proof of good faith if someone asks later.
None of this is complicated. It's a table, an endpoint and a commit. The hard part is remembering it needs to exist before someone needs it.
What if the federal government wins?
The courts may say federal law takes precedence and Utah can't do this. Kalshi goes back to operating in peace and the charges are dropped.
Even in that scenario, the lesson for devs stays the same. The case shows that a product can be right on paper and still be the target of a criminal case for years. The cost of being right in court is huge. The cost of having decent geoblocking from day one is an afternoon of work.
It's also worth remembering that political positions change with the administration. What the federal government defends today may not be what it defends four years from now. Code that depends on "the government is on our side" is code with an expiration date.
What I take from this
I don't have a settled opinion on whether prediction markets are gambling or derivatives. I think both readings have arguments, and that's why the case is in court and not on Twitter.
The part that interests me is simpler. Most of us will never be charged with a crime in Utah, thankfully. But almost everyone has written, or will write, code that decides who can do what and from where. That code is as critical as authentication code, and it usually gets a tenth of the attention.
If you're building something that deals with money or sensitive data, spend the afternoon. Put the block on the server, use 451, block what's unknown and give the list an owner. It's the kind of care I try to put into the projects I build, even when nobody asked for it.
LinkedIn summary
Kalshi's founders were criminally charged in Utah over an app that worked normally in the rest of the US. The company has a federal license. Even so, the state decided that license doesn't apply on its territory. Deploys have no borders, but the law does. And whether a Utah user sees the buy button is decided by an if in your code. Hiding the button on the frontend blocks nothing. The block has to live on the server, return 451 and treat unknown regions as blocked. The list of blocked regions is a business decision, not a forgotten constant in a config file. It needs an owner, a history and a log. I wrote the full article with the code and what I would do today. The link is in the comments, for anyone building products that deal with money or sensitive data. #SoftwareDevelopment #Compliance #Backend #Startups #Technology