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
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.
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.
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:
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": … }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-enrolcurl https://winkkey.net/api/merchant/auth-request/Abc…
# while pending: { "status": "pending", … }
# once consumed:
# {
# "status": "consumed",
# "consumedMethod": "winkkey",
# "winkAccessToken": "eyJ…",
# "winkAccessTokenExpiresAt": 1745…,
# "resultToken": "eyJ…",
# …
# }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;
}
});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.
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.
Auth + enrol links
Today's product. URL-driven, iframe-friendly, callback or poll.
iOS + web extension SDKs
Drop-in modules that pair with your existing Wink integration. One-line button alongside Sign-in with Wink.
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.