Palm sign-in and application approvals
One account supports browser sign-in and a connected palm scanner. Palm access requires a pilot invitation. Use veyns.signin({clientId, method: 'palm'}) to require palm. Applications receive the same pairwise subject for either method.
Scanner setup has two steps: scan the connection QR within 10 minutes, then verify your enrolled palm in the app within 5 minutes. Your account shows a countdown for each step. Pairing alone cannot approve a request.
New palm approvals require an active passkey and an active, verified scanner. If the API returns scanner_required, ask the user to connect a scanner from Your account before trying again. Keep the original idempotency key when recovering an existing request.
Veyns verifies palm captures with its palm service and binds the matched identity to the account. A removed scanner loses access. Reconnecting it requires palm verification again and cannot replace the account’s palm with another person’s.
Use the exact pairwise sub issued to your application. Internal account handles shown in administration are not application subjects.
Create a client secret in Applications. Your backend calls POST /v1/approvals with its signed-in user's subject, a stable idempotency key and exact action. The Android Approvals inbox asks the user to review and scan. Read the signed decision, atomically deduplicate your business operation, then acknowledge delivery.
Complete integration guide · OpenAPI contract
The Veyns Android app displays incoming requests while open. Users can also enable background notifications while their scanner is connected. The app checks for requests automatically; this pilot does not use push messaging or outbound webhooks. Action receipts require backend authentication, an action digest and a stable operation ID at /v1/actions/consume.
Option A: any OpenID Connect library
Point your library at the discovery document and use the code flow with PKCE. Client type is public: the code exchange uses PKCE, not a client secret. Subjects are pairwise.
issuer: __ORIGIN__ discovery: __ORIGIN__/.well-known/openid-configuration flow: authorization_code + PKCE (S256) client_id: from Applications redirect_uri: exactly as registered scopes: openid login id_token alg: ES256 (default) or ML-DSA-44 (opt-in)
Option B: the drop-in SDK
<script src="__ORIGIN__/veyns.js" defer></script>
<!-- human check, where a captcha would sit -->
<div class="veyns-widget" data-client-id="your_app"
data-callback="onVeynsVerified"></div>
<!-- passwordless sign-in -->
<div class="veyns-signin" data-client-id="your_app"
data-callback="onVeynsSignin"></div>
<script>
function onVeynsSignin({ claims, tokens }) {
// claims.sub is the account key, private to your app.
// tokens.refresh_token continues the session server-side.
}
</script>
Approvals bind a signature to one exact action; the consent screen shows the statement verbatim and the token carries its digest:
const { claims } = await veyns.approve({
statement: "Transfer $450.00 to Omar K.",
details: { amount: "450.00", currency: "USD", to: "omar-k" }
}, { clientId: "your_app" });
// claims.veyns_action.digest is bound to exactly this action.
The SDK runs the same code + PKCE flow internally (popup, response_mode=web_message) and verifies every token against the JWKS before your callback fires.
ID token claims
| Claim | Meaning |
|---|---|
iss / aud | The issuer and your client id. |
sub | Pairwise account key: stable for your app, meaningless anywhere else. No wallet key, device id, or biometric-derived value ever appears in a token. |
nonce, iat, exp, auth_time | Standard OIDC; ID tokens live 5 minutes. |
veyns_presence | true: a person completed the ceremony. |
veyns_intent | presence, login, or action. Check it matches what you asked for. |
veyns_level | VA1: sandbox assurance label. Use amr and auth_time to enforce the actual method and freshness; this label is not hardware certification. |
veyns_key_custody | A compatibility field, not attestation. The server does not trust client-asserted hardware custody. Use the required authentication method and explicit approval contract. |
veyns_action | Approvals only: { statement, digest }, digest = SHA-256 over canonical JSON of { statement, details } (keys sorted, no whitespace). Recompute and compare before executing anything. |
Verify on your server
Treat browser results as UX. Before trusting a token server-side, verify it against the JWKS like any OIDC token (use a library with ES256 support), check iss, aud, exp, nonce, and for approvals compare veyns_action.digest against your own canonical digest of the action you intend to execute.
Endpoints
| Endpoint | Purpose |
|---|---|
GET /.well-known/openid-configuration | Discovery. |
GET /jwks.json | Issuer keys: ES256 (EC P-256) and ML-DSA-44 (AKP, JOSE PQC draft). |
GET /authorize | Code flow entry; renders the ceremony from a server-side transaction, so the consent screen only ever shows the registered application name. |
POST /token | authorization_code (+PKCE) and refresh_token grants. Refresh tokens rotate; reuse revokes the family. |
GET /userinfo | Bearer access token → sub and presence claims. |
POST /revoke | RFC 7009 revocation. |
GET /logout | Ends the veyns session on the browser; optional post_logout_redirect_uri. |
POST /v1/clients | App registration (requires a signed-in developer; use Applications). |
The trust model
The account is the identity; your sub is derived from it and stays the same for as long as the account exists. Passkeys, browsers, scanners and the palm link are attachments that can be replaced without changing it. Browser sign-in requires two proofs the service verifies independently: a WebAuthn assertion with user verification from one of the account's discoverable passkeys, which identifies the account, and the ML-DSA-44 signature of that passkey's browser wallet over a server-built envelope (protocol version, client, intent, action digest, transaction lifetime). A browser that lost its wallet key attaches a replacement only after that passkey verifies. Accounts survive lost devices through synced passkeys, one-time recovery kits (account id and recovery code) and device linking, and individual passkeys can be removed from the account page. A new account, and therefore a new sub, is created only when a person explicitly asks for one.
Sandbox boundaries
This is a controlled sandbox. Palm access requires admission and a linked scanner. Security state is stored in encrypted SQLite transactions. No production hardware assurance, customer isolation or availability claim is made. Plain-language policies: security, privacy, terms.