← All posts

How PartyPass makes an NFC invitation that can't be forwarded

The engineering behind PartyPass: a host-carried NTAG 424 DNA tag that writes a new signed link on every tap, server-side signature checks, replay refusal, and invites locked to the phone that was tapped. Why NFC alone doesn't stop sharing, and what does.

Joshua Gilmer··7 min read

Every digital invitation has the same weakness: it's a link. Text it, email it, drop it in a group chat, and anyone who has the link has the invite. For a block party that's fine. For the events where the guest list is the whole point, it isn't.

PartyPass is our answer. It's a small NFC tag the host keeps. When you meet someone you want at your event, you hold your PartyPass to their phone and their invitation opens right there. One tag can invite thousands of people, and none of them can pass the invite along. This post explains how that works.

In short:

  • The tag writes a different, cryptographically signed link on every tap.
  • Our server checks the signature before it shows anything.
  • A link that has already been opened is refused if it arrives again.
  • The invite only opens on the phone that was tapped.

An ordinary NFC tag doesn't stop forwarding

A plain NFC tag stores one web address. Tap it and your phone opens https://example.com/invite/ABC123. Tap it again and it opens the same address. Copy that address from your browser, send it to a friend, and they're in.

Most "NFC invitation" products work this way. A card with a chip is handed or mailed to each guest, and the chip is a shortcut for opening a link. It isn't a security boundary. If you want an invite that can't be passed along, two things have to change: the chip has to produce something new every time, and a server has to check it.

The chip writes a new link on every tap

PartyPass tags use the NXP NTAG 424 DNA, a chip NXP positions for product authentication and event ticketing. Its relevant feature is called Secure Unique NFC, or SUN. (NXP's documentation also calls it Secure Dynamic Messaging.)

With SUN turned on, the chip doesn't emit a fixed address. On every tap it:

  1. Adds one to an internal tap counter.
  2. Encrypts its unique ID and that counter with AES-128. NXP calls this block PICCData.
  3. Computes a message authentication code, an AES-128 CMAC, over that block using a key stored on the chip.
  4. Writes the encrypted block and a shortened form of the code into the web address it hands the phone.

So the address the guest's phone opens looks like .../api/nfc/verify?uid=<32 hex chars>&cmac=<16 hex chars>, and both values change on every tap. The same tag never writes the same link twice. That is what lets one tag serve an unlimited number of guests.

The server checks the signature before it does anything else

Our verification endpoint follows NXP's application note AN12196 step by step:

  1. Decrypt the block to recover the chip's real ID and the tap counter.
  2. Derive the per-tap signing key from those values, the way the chip did.
  3. Recompute the signature and compare it to the one in the link, using a constant-time comparison so timing can't leak anything.

If the signature doesn't match, the tap is refused. That covers edited links and guessed links. Only after the signature checks out do we look up the tag, the event it's assigned to, and its status. A chip we haven't registered is refused, and so is a tag the host has deactivated.

Why a forwarded link fails

Suppose a guest is tapped, copies the link out of their browser, and sends it to a friend. The link is real and its signature is valid. What stops the friend?

Two things.

The tap counter. Every verified tap is recorded with its counter. When a link arrives with a counter we've already seen, we answer "Tap already used." A counter lower than the last one we recorded is refused too. A forwarded link is, by definition, a link that has already been opened once.

The changing ID. For chips configured without a mirrored counter, the encrypted block itself changes every tap, so a copied link is still a one-time value.

Either way, by the time a guest has a link to forward, that link has already been spent.

The invite only opens on the phone that was tapped

Refusing the tap link is the first gate. The second is the page it opens.

A successful tap creates an access grant for that event, tied to a fingerprint of the phone that received the tap. When the event page checks the grant, a request from a different device gets:

This link can only be used from the device that tapped the PartyPass.

The fingerprint is a hash of the phone's user agent and network address. It isn't a perfect device identity, and we don't claim it is. It doesn't need to be. Together with the single-use tap, it means both things a guest could pass along, the tap link and the page it opens, fail on anyone else's phone. Getting around it would take deliberate effort from someone who understood what they were doing, which is more than a party invitation needs to defend against. If an event ever calls for more, we can add a step like a PIN on top of the same foundation.

What the guest sees

None of the above. On iPhone, the tap launches an App Clip, a small piece of the Luau app that runs without a download: "You're in(vited)", the event, and three buttons, I'm going, Maybe, Can't make it. On Android, the tap opens the event page in the browser. First-time guests confirm a phone number with a text code. That's the whole setup, and organizations can brand both the tag and the App Clip screen.

What the host controls

A few switches sit on top of the mechanism, all in the event's PartyPass Settings:

  • Require PartyPass Tap to RSVP. With this on, anyone who hasn't been tapped sees a page asking them to get tapped instead of the event details. Hosts and co-hosts always get through, and the Share button is hidden.
  • RSVP time limit. Give each tap a window to respond: 15 minutes, 1 hour, 4 hours, or 24 hours, like holding a seat at the movies. Guests see a countdown, and a fresh tap opens a fresh window.
  • Check-in. Once the event's check-in window opens, a tap checks the guest in instead of inviting them. Tap arriving guests at the door, or mount a tag for them to tap, and watch the attendance list fill from the event page. Unlike a QR code that opens a form, a check-in can't be texted to friends who never showed up.
  • Deactivate. A lost tag can be switched off from the dashboard, and its taps stop working immediately.

Every claim, assignment, release, and tap is logged, so a host can see how many times each tag was used and when.

Why a tag, and not just a smarter link

We could have built "non-forwardable" invites purely in software, with personalized links and device checks. Plenty of products do. But a personalized link is still a link, and the guest still receives it somewhere they can forward from.

A tag the host carries changes the shape of the problem. The invitation is extended in person, phone to phone. The tag writes a fresh, signed link on demand and never the same one twice. The server refuses anything it has seen before and anything opened on the wrong phone. There is no link in the guest's inbox to forward, because the link didn't exist until the tap, and it was spent the moment it opened.

If you're building something like this yourself, the parts are public: NTAG 424 DNA chips, NXP's AN12196 for the verification steps, and a server that records counters and refuses repeats. If you'd rather not build it, PartyPass works with any free Luau event, tags come in any form factor with your branding, and the setup guide covers claiming and assigning them. To get tags, email reach@luau.co with a line about your event or group.