DEV Community

Profile Tap
Profile Tap

Posted on

Building a Lost-Pet Recovery System with NFC + QR: A Practical Architecture

A metal tag engraved with a phone number has three failure modes: the number changes, the engraving wears off, and the finder has to call a stranger.

Better model: make the pet's collar a URL.

The tag is a pointer, not the data

Don't store data on the tag. An NTAG215 chip holds ~504 bytes — enough for a URL, not a profile. And you could never update it.

Instead:

NFC tag  ->  https://example.com/p/{pet_id}
QR code  ->  https://example.com/p/{pet_id}
Enter fullscreen mode Exit fullscreen mode

The tag is a pointer. The profile lives on the server. That gives you mutable data (owner changes their number, no new collar), scan analytics, field-level access control, and revocability.

The physical tag becomes dumb infrastructure. That's the point. This is the model we use for ProfileTap's pet ID tags.

Why NFC and QR

They're not redundant — they cover different failure modes.

NFC wins when:

  • It's dark — no camera, no aiming
  • The surface is scratched or dirty
  • You want a one-second, zero-thought interaction

QR wins when:

  • The finder's phone has no NFC chip
  • The finder doesn't know what NFC is, or where to tap
  • The tag is behind glass, or out of tapping reach

In India, a large chunk of phones don't have NFC. Ship NFC-only and half your finders can't use it. Print both on the same tag. The QR is your fallback — and fallbacks aren't optional when the failure mode is "the pet doesn't come home."

The same dual NFC+QR approach applies well beyond pets — we use it for vehicle windshield tags and luggage tags too.

Writing the tag

NDEF is the standard. One record, type URI:

const ndef = new NDEFReader();
await ndef.write({
  records: [{ recordType: "url", data: "https://example.com/p/abc123" }]
});
Enter fullscreen mode Exit fullscreen mode

Lock the tag after writing. NTAG215 has a one-way lock bit. An unlocked tag can be overwritten by anyone with a phone — including someone who doesn't want the pet returned.

Design for a stranger, not the owner

The person scanning is a stranger who found a scared animal on the road. They have ~30 seconds of patience.

  • No login. No app install. Web only.
  • One primary action above the fold — a big "Call Owner" button.
  • Server-render it. They may be on weak 3G on a roadside.
  • No home address. Anyone who scans a stray tag would then know an address and that the resident owns a pet. Keep it server-side.

Location: ask, don't take

Tell the owner where the pet was found:

navigator.geolocation.getCurrentPosition(
  (pos) => notifyOwner(pos.coords.latitude, pos.coords.longitude),
  () => {} // declined - degrade gracefully
);
Enter fullscreen mode Exit fullscreen mode

But never gate the phone number behind the permission prompt. A system that holds the pet's rescue hostage to a browser dialog is a system that fails.

Privacy is the whole product

A scannable public URL is, by definition, a public URL.

  • Non-sequential IDs — random base62, not auto-increment. Enumeration should be pointless.
  • Rate-limit by IP — someone hitting 500 profiles isn't looking for a lost dog.
  • Call masking — route calls through a proxy number so the owner's real number is never exposed. We wrote about this pattern in more depth here.
  • Log every scan so the owner can see the history.

Wrapping up

The stack is boring — a URL, an NDEF record, a server-rendered page, a notification queue. That's a feature. The hard parts aren't technical; they're the 30 seconds of attention you get from a stranger on the roadside.

We're building this at ProfileTap — NFC + QR smart profiles for pets, vehicles, families and businesses, built India-first.

How did you handle the location-consent tradeoff? Drop a comment.

Top comments (0)