Skip to main content

Magic Links and the Claim Flow

When a brand publishes a Digital Product Passport, the data is anchored on-chain — but the passport is not automatically attached to anyone's wallet. The first buyer of the product still has to claim their passport, and the brand has to make sure only the legitimate first owner can do this.

This page explains the claim mechanism, with a focus on magic links — the cryptographic equivalent of a Wonka golden ticket inside the box.

A magic link is not a way to bypass authentication. The dpp app always requires the buyer to sign in to Keyban (Email OTP, Phone OTP, Google, or Auth0) before any claim can succeed — that is how the buyer gets an embedded wallet to receive the ERC-721 in.

What the magic link does allow is claiming without the brand needing to know the buyer in advance. The brand prints the link or QR code on the packing slip; whoever physically receives the parcel can claim. The brand never asks for the buyer's email or phone number. From the brand's perspective the buyer stays anonymous, even though Keyban itself authenticated them.

The model is two-layered:

  • Layer 1 — Keyban authentication (always required): the buyer signs in and gets an embedded wallet. Keyban knows who they are; the brand does not.
  • Layer 2 — Claim eligibility (configured by the brand): the buyer must additionally satisfy a gating rule. Magic link, email/phone pre-registration, or both.

Without any layer-2 gating, anyone with a Keyban account could claim any unclaimed passport just by knowing its URL — that is why a brand always picks at least one rule.

Think of a magic link as a digital golden ticket:

  • The brand generates one per passport before shipping the product.
  • It is printed inside the box, emailed to the customer, or embedded in the packing slip QR code.
  • The buyer scans it with the dpp app, and the app uses it as proof that they have the physical product in hand.

A magic link is:

  • Bound to a single passport — it cannot be reused on another product.
  • Cryptographically signed by the brand — counterfeiters cannot forge a valid one.
  • Single-use — once the passport is claimed, the same link cannot mint a second token.
Lifetime

The current implementation does not embed an expiration date in the link itself. A magic link stays valid until either the passport is claimed or the brand rotates its signing secret (which invalidates every outstanding link at once). Per-link expiration is a planned enhancement.

The claim flow at a glance

  1. Step 1: Generate

    Brand creates a magic link bound to the passport's UUID.

  2. Step 2: Ship

    Link travels with the product — packing slip QR, sticker, scratch panel.

  3. Step 3: Open & sign in

    Buyer opens the dpp app via the link, signs in to Keyban, gets a wallet.

  4. Step 4: Mint

    App validates the link and mints the ERC-721 to the buyer's wallet.

Four-step flow: brand generates a magic link tied to one passport, ships it with the product, the buyer opens the dpp app and signs in to Keyban, then the app validates the link and mints the ERC-721.

What the buyer actually sees

From the buyer's perspective, only two actions are required: scanning the link, and signing in. Everything after that runs automatically — there is no "Claim my passport" button to tap.

dpp.your-domain.com
dppFirst-buyer claim journey
  1. Scan the link

    Buyer scans the QR or taps the link printed on the packing slip.

  2. Sign in to Keyban

    Email OTP, Phone OTP, Google, or Auth0 — the buyer picks one method. An embedded wallet is provisioned silently in the background.

  3. Auto-claim runs

    As soon as sign-in completes, the app posts the link to Keyban — no extra button to tap. A spinner reads 'Claim in progress'.

  4. Passport in wallet

    ERC-721 minted to the buyer's wallet. Transaction hash and certified attributes are shown on the same screen.

The brand is out of the loop after step 1: from sign-in onwards, everything happens between the buyer's phone, Keyban's API, and the blockchain.

Three ways to gate a claim

Keyban authentication is always required — these gating rules sit on top. The brand picks the one that matches their commerce model:

  1. Magic link

    Brand stays out of identity

    • Brand does not need to know the buyer's email or phone
    • Whoever physically receives the parcel can claim
    • If the parcel is intercepted in transit, the interceptor can claim
    • Best for: gifts, anonymous retail, marketplaces, products with no checkout email
  2. Email or phone pre-registration

    Brand knows the buyer

    • Brand pre-registers the customer's contact at checkout
    • A stolen parcel does not let the thief claim — they cannot sign in as the buyer
    • Brand must collect contact info at purchase time
    • Best for: e-commerce flows where the buyer already has an account with the brand
  3. Magic link + pre-registration

    Both factors

    • Highest assurance: physical parcel possession AND verified buyer identity
    • Defeats both transit interception and account-credential reuse
    • Most friction; the brand must run both flows in parallel
    • Best for: high-value goods, regulated products, warranty registration
Three approaches the brand can use to gate a claim, on top of mandatory Keyban authentication: magic link travelling with the parcel, email or phone the brand pre-registered for the customer, or both combined.

Behind the scenes: claim sequence

When a buyer opens the dpp app with a magic link, here is what happens between the front-end, the Keyban backend, and the blockchain:

  1. Buyerprimary
  2. dpp appsystem
  3. Keyban authsystem
  4. Keyban APIsystem
  5. Mint job queuesystem
  6. Starknetsystem
  1. 1Scan QR / open the magic link
  2. 2AuthGuard: redirect to sign-in (Email OTP / Phone / Google / Auth0)
  3. 3Session + embedded wallet provisioned
  4. 4POST claim with the magic link (session cookie attached)
  5. 5Validate link, resolve buyer Account from session
  6. 6Enqueue mint job for the passport -> buyer wallet
  7. 7201 Created
  8. 8Job picked up
  9. 9Check status (published, claim available)
  10. 10ERC-721 mint(buyer.wallet, passport.tokenId)
  11. 11Tx hash
  12. 12Passport in your wallet
Buyer opens the dpp app, signs in to Keyban which provisions an embedded wallet, then the app posts the magic link, the backend validates it and enqueues a mint job, the consumer mints the ERC-721 to the buyer's wallet.

A few things worth noting from this sequence:

  • The buyer signs in first. The dpp app's AuthGuard redirects unauthenticated visitors to a Keyban sign-in screen before the magic link can be posted. The session cookie issued at sign-in is what lets the API know whose wallet to mint to.
  • The link does not carry the wallet address. It only identifies the passport. The buyer's wallet address comes from the authenticated session, not from the link itself. This is what makes the magic link safe to print on a packing slip — even if a stranger reads it, they cannot redirect the mint to themselves without going through Keyban auth as the signed-in buyer.
  • Eligibility checks happen in the consumer, not in the controller. The controller's job is to validate the link and resolve the buyer; the consumer makes sure the passport is still claimable when it actually mints.
  • The cryptographic signature is checked on the API side, never in the app. The app only forwards the link verbatim — it has no secret to verify with.

What can go wrong?

ScenarioOutcome
Link reused after a successful claimSignature is still valid, but the consumer rejects the mint because the passport is already claimed.
Link tampered withSignature does not match the brand's secret — rejected.
Link used on a different passportThe link is bound to one passport; trying to claim another fails.
Brand's signing secret leaksBrand rotates the secret; every outstanding link becomes invalid at once.

Recap

QuestionShort answer
What is a magic link?A signed, single-passport URL acting as proof of physical possession of the parcel.
Where does it come from?The brand generates it via the Keyban API and ships it with the product.
Can it be forged?No — the cryptographic signature requires the brand's secret.
Can it be reused?No — once the passport is claimed, future redemption attempts fail.
Does the buyer need a Keyban account?Yes — the dpp app always requires sign-in before a claim. The link is layered on top of authentication, not a substitute for it.
Is a magic link always required?No — the brand can also gate by pre-registered email or phone, or combine both with a link.

For developers

This section covers the implementation — JWT structure, API contract, security guarantees — in case you need to integrate with Keyban or audit the system.

The magic link is a URL of the form https://dpp.brand.com/?magicToken=<JWT>. The query string carries a JWT signed with HMAC-SHA-256:

// Header
{ "alg": "HS256", "typ": "JWT" }
// Payload
{
"jti": "product-uuid-here"
}
  • alg: HS256 — HMAC-SHA-256 with the brand's per-organization secret.
  • jti — the passport UUID. Verified to match the passport being claimed.

The signature is verified with jose's jwtVerify against the configured HMAC secret. Because jti is bound, a JWT issued for product A cannot be replayed against product B.

The current implementation does not set iat or exp on the JWT, and jwtVerify is called without expiration constraints. A link is therefore valid as long as the brand's HMAC secret is unchanged and the passport is still claimable. Adding a per-link expiry (exp) is a low-effort enhancement (see setExpirationTime in jose) but not yet implemented.

API endpoint

The claim endpoint is POST /dpp/claim/magicToken. It validates the JWT signature, resolves the passport, and enqueues a mint job. The mint consumer is responsible for the downstream eligibility checks (passport status, claim status) and for calling the ERC-721 contract; the controller stays thin so the buyer always gets a fast response.

Source: backend/nestjs/src/dpp/controllers/claim.controller.ts and backend/nestjs/src/dpp/services/magic-token.service.ts.

The route name (magicToken) reflects the JWT carried inside the URL; the user-facing name (in admin UI and docs) is magic link.

Distribution patterns

ChannelFormatTrade-off
Packing slip QRURL with ?magicToken=... encoded as a QRBuyer scans with their phone — zero friction, no app required.
EmailOne-click link in a transactional emailWorks post-delivery; survives a missing packing slip.
SMSShort link in a transactional SMSHigh open-rate; pairs well with phone OTP gating.
Scratch panel inside the boxLink printed under a peelable layerTamper-evident; defeats interception during shipping.

Threat model

Magic links defend against the following adversaries:

  • A counterfeiter — cannot forge a valid link without the brand's HMAC secret.
  • A passive observer — cannot replay the link for a different product (the jti is bound).
  • An impatient attacker — cannot guess a valid link (HMAC signatures are unguessable).
  • A snooping intermediary — cannot redirect the mint to their own wallet, because the recipient address comes from the authenticated session, not from the link itself.
  • A late attacker (after claim) — once the passport is claimed, the consumer rejects the mint even though the link's signature is still cryptographically valid.

Magic links do not defend against:

  • Physical interception in transit — whoever physically holds the parcel and signs in to Keyban can claim. Mitigation: combine with pre-registered email or phone.
  • A late attacker (before claim) — because the JWT does not carry an exp claim today, a leaked but unclaimed link stays usable indefinitely. Mitigation: rotate the HMAC secret to invalidate every outstanding link.
  • Brand secret leak — if the HMAC secret is exfiltrated, attackers can mint arbitrary links. Mitigation: rotate the secret immediately and invalidate outstanding links.

Security checklist for integrators

  • Rotate the HMAC secret periodically and after any suspected leak. This is currently the only way to invalidate outstanding links.
  • Pair magic links with email/phone pre-registration for high-value goods, since unclaimed links stay valid until the secret rotates.
  • Monitor failed claim attempts on a single passport — multiple failures may indicate a brute-force attempt or a tampered link.
  • Track unclaimed passports and follow up with buyers; a long-lived unclaimed link increases the window for parcel interception.