Most online Secret Santa tools work the same way: the organizer types in everyone’s names, the website draws, and the website stores who buys for whom until everybody has looked. That means a company you have never heard of keeps a list of your family, your colleagues, their email addresses and their wish lists — usually behind an account you had to create for one evening in December.
Our Secret Santa Generator takes a different route: the draw happens in the organizer’s browser, and each person’s match travels inside their own link, encrypted, in a part of the address that browsers never send to a server. This article explains how that works, what it protects against, what it doesn’t, and how the same idea lets people drop out or join late without a new draw. We are not the only generator that keeps the match in the link; this is how ours does it, including the parts we can’t protect.
The Short Answer
A web address can end with a fragment: everything after the # sign. When you open a link, your browser splits the address, requests the part before the # from the server, and keeps the fragment to itself. That is not a trick or a setting; it is how URLs are defined (RFC 3986, section 3.5: the fragment is separated from the rest of the address “prior to a dereference”). The fragment is also stripped from the Referer header when you click on to another site.
So a Secret Santa link looks like this:
https://webutil.io/secret-santa-generator/reveal/#1BWdCNino94p9A9j_oYqAx5WPGb…
Our server receives a request for /secret-santa-generator/reveal/ and returns the same page it returns to everyone. JavaScript on that page reads the fragment, decrypts it and shows “You’re the Secret Santa for Ben”. The names, the budget and the wish list never reach us, so there is nothing for us to store, leak or delete.
What Is Inside the Link
For each person, the generator builds a small JSON object: the event name, the giver, the receiver, the budget, the date and the receiver’s wishes. Then it seals it:
- Pick a fresh random 128-bit key and a random 96-bit IV with
crypto.getRandomValues. - Encrypt the JSON with AES-GCM through the browser’s built-in Web Crypto API (
crypto.subtle.encrypt). - Put key, IV and ciphertext one after the other, encode them as URL-safe Base64, and add a version digit in front.
The result is about 210–240 characters after the # for a typical person, short enough for WhatsApp, SMS, email or a QR code on a paper slip.
Wait — the key is in the link?
Yes, and it is worth being plain about what that means. Anyone who has the whole link can open it. The encryption is not what keeps the match away from our server; the fragment does that. What the encryption adds is this:
- Nothing readable at a glance. Without it, a link might literally contain
giver=Emma&receiver=Ben, visible in a chat preview, a browser history dropdown or over someone’s shoulder. Plain Base64 would be one click in a decoder away from the same thing. - Damaged links fail loudly. AES-GCM includes an authentication tag. If a messenger cuts off the last characters, or someone edits a letter to “see what happens”, decryption fails and the page says the link is incomplete instead of showing a wrong match.
It is not a signature. Anyone could use the generator to make a new, valid-looking link that says whatever they like. Within a group of friends that isn’t a real threat, and a signature would need a secret that lives somewhere — which is exactly the server we are trying not to need.
What the Organizer Could Still Do
Paper slips in a hat have one honest weakness: whoever holds the hat could unfold a slip. Links have the same weakness. The organizer’s browser creates every link, so the organizer could open any of them.
The generator has an option “I’m taking part — don’t show me who got whom”. With it, the organizer sees names and links but never the matches on screen. That stops accidental spoilers, not a determined organizer with developer tools. If that matters in your group, let everyone open their own link first, or ask someone who isn’t taking part to organize.
The organizer also gets an organizer link. It holds the whole draw — names, wishes, optional email addresses, exclusions and who buys for whom — sealed the same way. It is what makes the next part possible without an account. It also means that link is the one to keep private.
Someone Drops Out: One New Link, Not a New Draw
The usual advice when someone cancels after the draw is to draw all names again and resend every link. That annoys everyone who has already started shopping. Because the organizer link contains the full draw, the generator can repair it locally instead:
- Say Emma buys for Ben and Ben buys for Cara. Ben drops out. Emma simply takes over Cara. Only Emma gets a new link; everyone else’s match stays the same.
- That fails in two cases: when Emma and Ben had drawn each other (Emma would end up buying for herself), or when Emma isn’t allowed to buy for Cara because of an exclusion. Then the generator swaps with a third person who can take Cara, so two links change.
A late joiner works the other way round. Picture the draw as arrows from giver to receiver. The newcomer is slotted into one arrow: if Anna bought for Dan, Anna now buys for the newcomer, and the newcomer buys for Dan. Anna’s link changes, and the newcomer gets one. Nobody else notices anything.
The honest trade-off: after a repair, the draw is no longer a perfectly fresh random draw, and the person who gets a new link learns that something changed. For a gift exchange, that is a much better deal than starting over.
Impossible Exclusions and Hall’s Theorem
Exclusions keep couples from drawing each other. In our generator they always work in both directions. But exclusions can make a draw impossible, and many tools either loop forever, quietly ignore a rule, or show a vague error.
A small example: Anna, Ben and Cara, with an exclusion between Anna and Ben. Nobody can buy for themselves, so Anna can only buy for Cara, and so can Ben. Two givers, one possible receiver: no valid draw exists.
The general rule is Hall’s marriage theorem: a draw exists exactly when every group of givers, taken together, still has at least as many possible receivers as there are people in the group. When it fails, there is always a group that is “stuck”. So the generator names it, and you get “No valid draw with these exclusions: Anna and Ben together could only give to Cara” instead of “something went wrong”.
How the draw itself works:
- Shuffle and check. Shuffle everyone with a Fisher–Yates shuffle fed by
crypto.getRandomValues, using rejection sampling so no position is more likely than another. If nobody drew themselves and no exclusion is broken, done. Without exclusions about 37% of shuffles pass (that is 1/e), so this takes a few tries. Every valid draw is equally likely. - Fall back to matching. With many exclusions, valid shuffles can get rare. After 2,000 failed tries, the generator switches to a bipartite matching algorithm (Kuhn’s augmenting paths, with random order). It is guaranteed to find a draw if one exists. If it can’t, the givers and receivers it visited on its last attempt are exactly the stuck group from Hall’s theorem.
One Thing We Had to Fix on Our Own Site
“Browsers don’t send the fragment” is only half the story. JavaScript on the page can read it, and a script can send it anywhere. Analytics and error-tracking scripts often report location.href, which includes the fragment.
While writing this article we checked our own pages and found exactly that: our self-hosted visitor counter reported the full address. The server cut off the fragment before saving anything, but the fragment was still sent. Since 24 October 2026 the Secret Santa pages take the fragment out of the address for the moment the counter runs and put it straight back afterwards, so the counter only sees /secret-santa-generator/reveal/. If you build something similar, check every script on the page, not only your own.
What This Approach Can’t Do
- No recovery. If the organizer loses the organizer link, drop-outs and late joiners need a new draw. We have no copy.
- No reminders from us. We can’t email anyone, because we don’t know anyone. The reveal page offers an “Add to calendar” file with reminders instead, which your own calendar keeps.
- No self sign-up. “Send one link and let people add themselves” needs somewhere to collect the names, which means a server. That would be a different tool with its own rules for deleting data.
- The link is the secret. Anyone it is forwarded to can open it, just as anyone can read a forwarded paper slip.
Frequently Asked Questions
Can webutil.io see who drew whom?
No. The match is in the part of the link after the #, which browsers don’t send to servers, and our visitor counter on these pages only sees the address without it. We only see that a reveal page was opened.
Is the encryption strong enough?
AES-GCM with a random 128-bit key is a standard choice. But since the key travels with the link, the protection comes from keeping the link private, not from the cipher. The cipher’s job is to stop casual reading and to detect damaged links.
Why not just store the draw on a server like other sites?
A server copy makes some features easier (reminders, recovery, self sign-up). It also means somebody keeps a list of your group. For a one-evening gift exchange, we think “nothing to store” is the better default.
What happens if a messenger cuts the link off?
Decryption fails because the authentication tag doesn’t match, and the page asks the person to get the full link again. It never shows a wrong match.
Ready to try it? Draw names with the Secret Santa Generator — no sign-up, and nothing to delete afterwards.