WinkKey · built on Wink Identity

Biometric verification by URL.
Without the camera every time.

WinkKey lets any merchant generate a one-time link that proves a user’s identity. The user taps it, verifies with Face ID or a Wink face match, and the merchant receives a signed identity claim plus a fresh Wink access token — server-to-server, in under a second.

The non-negotiable

Every successful WinkKey verification releases a usable Wink access token to the merchant. Same credential they’d get from a full face match, but earned via Face ID. That’s the bar. Anything that fails it isn’t WinkKey.

What it does

Everything below is in production at winkkey.net today.

Two link types, one API

Auth-links verify existing users; enrol-links onboard new ones. Same endpoint, one flag (`kind: "enroll"`) flips the flow. The merchant integration doesn't care which is which.

Passkey-first, cloud-safe

Returning users skip the camera — Face ID / Touch ID via WebAuthn passkeys, ~1 second end-to-end. New devices, expired sessions, or never-enrolled users fall through to a Wink cloud face match. Either way the merchant gets a token.

Stale-vault recovery

If our cached refresh token has expired (Wink's 10h SSO max), the user is automatically stepped up to a fresh Wink sign-in. The merchant never sees a stale token.

Iframe & popup-aware

Embed /v2/<env>/auth/<rid> as an iframe in any host — ChatGPT Apps SDK, your own web app, anywhere. Storage partitioning, third-party cookies, and Permissions Policy chains for camera all handled. WebAuthn challenges are keyed by link ID, not session.

Operator-customisable copy

Per-link overrides for the page title, body text, and CTA label. Defaults are kind-aware (auth vs enrol). API or admin form, your pick. Useful when one merchant's voice differs from another's.

Captcha mode

Skip the user-hint email and the link accepts any successfully-verified human. Biometric proof-of-personhood as a primitive. No data collection beyond the verification event itself.

Real audit trail

Every link records who created it (admin user, MCP shim, or specific end-user via on-behalf-of), who consumed it, when, and via which method (passkey vs cloud). Visible in the admin dashboard, exposed in the merchant poll response.

Email delivery built in

Generate a link and email it to the recipient with one click. SendGrid-backed, DKIM-aligned sender, soft language tuned for inbox placement on M365 and iCloud Mail.

Multi-env, single deploy

One backend talks to dev / qa / stage / prod Wink Cloud realms simultaneously. Each API key is bound to one env at issue time; first-party UIs follow the admin's active-env selector. End-user URLs carry the env as a path segment (/v2/<env>/auth/<rid>) for observability at every layer.

Env-aware passkey picker

WebAuthn allowCredentials is filtered server-side by the env the link is bound to. A user with passkeys in multiple envs sees only the right one in iOS Keychain's picker — registration time stamps `(<env>)` on user.name so the labels disambiguate visually too.

Runtime mobile SDK config

iOS app fetches its Wink-SDK signing PEM + keyId + merchantId from the server at launch instead of bundling them in the binary. Rotate creds, swap envs, ship new clients — no app update required. PEM is envelope-encrypted at rest, gated by a narrow `signing-key:read` scope, cached on-device for offline resilience.

Scoped API keys

Issue narrow-capability keys for mobile bootstrap or future SDK customers — `signing-key:read` lets a bundled client read its env's signing PEM and nothing else. Extracted from an IPA, an attacker gets the PEM but can't create auth-links or mint VCs.

How it works

1

Merchant creates a link

POST to /api/merchant/auth-request from your backend. Specify auth or enrol, optional callback URL, optional user-hint email. We return a one-time URL.

2

User taps the link

Lands on /v2/<env>/auth/<rid>. If they're a returning WinkKey user on this device, Face ID prompts immediately. Otherwise Wink cloud face match. Either way: a few seconds, one tap.

3

Merchant gets the credential

Either via your callback URL with ?token=<JWT>, a postMessage event if you embedded the page in a popup/iframe, or by polling /api/merchant/auth-request/<rid>. The poll response carries the result token AND a fresh Wink access token, ready to call Wink APIs as the user.

Two minutes to a verified user

Server-to-server integration is a single bearer token. Get a key from us, then:

Create an auth-linkbash
curl -X POST https://winkkey.net/api/merchant/auth-request \
  -H "Authorization: Bearer wkey_…" \
  -H "Content-Type: application/json" \
  -d '{
    "ttlSeconds": 900,
    "userHintEmail": "user@example.com",
    "callbackUrl": "https://acme.com/cb"
  }'

# returns:
# { "rid": "Abc…", "url": "https://winkkey.net/v2/stage/auth/Abc…", "expiresAt": … }
Create an enrol-linkbash
curl -X POST https://winkkey.net/api/merchant/auth-request \
  -H "Authorization: Bearer wkey_…" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "enroll",
    "callbackUrl": "https://acme.com/cb"
  }'

# Forces Wink cloud, 2-day TTL by default,
# user is offered a WinkKey passkey post-enrol
Poll for completionbash
curl https://winkkey.net/api/merchant/auth-request/Abc…

# while pending: { "status": "pending", … }
# once consumed:
# {
#   "status": "consumed",
#   "consumedMethod": "winkkey",
#   "winkAccessToken": "eyJ…",
#   "winkAccessTokenExpiresAt": 1745…,
#   "resultToken": "eyJ…",
#   …
# }
Listen for completion via postMessagejs
window.addEventListener('message', (e) => {
  if (e.origin !== 'https://winkkey.net') return;
  if (e.data?.type !== 'winkkey') return;

  switch (e.data.event) {
    case 'auth-link:done':
      // e.data.token = result JWT
      // e.data.callbackUrl
      break;
    case 'auth-link:cancelled':
    case 'auth-link:mismatch':
    case 'auth-link:invalid':
      // handle terminal failure
      break;
  }
});
Full API reference →bearer keys, error reasons, per-link copy overrides, repeat / renew / cancel / send-email, callback URL contract, postMessage events

Have an invite code?

Request a WinkKey API key

Onboarding partners get a one-time invite code from a WinkKey admin. Paste it on the request page; we'll mint a key tied to your label. It activates after admin approval.

Request a key →

What’s next

Auth-links and enrol-links are stable and in production. The next chapters are about packaging the same flows behind drop-in SDKs.

Live

Auth + enrol links

Today's product. URL-driven, iframe-friendly, callback or poll.

Designed

iOS + web extension SDKs

Drop-in modules that pair with your existing Wink integration. One-line button alongside Sign-in with Wink.

Pending Wink approval

Multi-tenant + per-tenant subdomain

Each tenant gets <name>.winkkey.net with isolated rpId. Server-side broker exchanges tokens against Wink so we never hold tenant client_secrets.