Upvera for Developers
Identity

Sign in with Upvera

Upvera is an OpenID Connect provider that proves the person signing in to your product is a real human. Your site gets proof, not identity: every site knows the person by an ID made just for it.

The provider is live at https://id.upvera.co. Client registration opens in the Upvera app with its next release, so you can build against discovery today and register your client when the screen ships.

What you get

  • Sign-in closed to bot farms. Every sign-in carries the person's proof-of-personhood tier: browser key, device key or device attestation.
  • Verified people who sign in to your product become Traction proof for your startup on Upvera. The count is observed at our provider.
  • No email by default. If you ask for it, it comes only when the person allows it.

Four steps

  1. Verify your domain on Upvera with a DNS TXT record or a .well-known file.
  2. In your startup panel, open Sign in with Upvera › Add client. Redirect URIs must sit under your verified domain.
  3. Give your OpenID Connect library the discovery URL. Authorization code flow, PKCE S256 and nonce are required.
  4. Place the button, verify the ID token against the JWKS and check iss.

Button

The proof button says what it gives. Use the dark button on light backgrounds and the light button on dark ones. Don't change its text or shape.

Sign in with Upvera, dark button Sign in with Upvera, light button

Download the SVG and serve it from your own site: dark · light · Turkish: dark · light.

html
<a href="/auth/upvera">
  <img src="/assets/upvera-login-dark-en.svg" alt="Sign in with Upvera" width="360" height="56">
</a>

Endpoints

Issuerhttps://id.upvera.co
Discoveryhttps://id.upvera.co/.well-known/openid-configuration
authorizehttps://id.upvera.co/authorize
tokenhttps://id.upvera.co/token
userinfohttps://id.upvera.co/userinfo
jwkshttps://id.upvera.co/.well-known/jwks.json
bash
curl -s https://id.upvera.co/.well-known/openid-configuration \
  | jq '{issuer, scopes_supported, subject_types_supported}'
json
{
  "issuer": "https://id.upvera.co",
  "scopes_supported": ["openid", "profile", "email", "upvera:founder"],
  "subject_types_supported": ["pairwise"]
}

Rules

  • Authorization code flow only (response_type=code). There is no implicit flow.
  • PKCE S256 is required. plain isn't accepted.
  • nonce and state are required. Check iss in the response (RFC 9207).
  • redirect_uri must match the registered one exactly, with no query and no fragment.
  • ID tokens are ES256 with a kid. Keys rotate: cache the JWKS, and refresh it when you see an unknown kid.
  • A code lives 60 seconds and works once. A second use also revokes the tokens issued from it.
  • If you use an identity broker such as Supabase Auth, Clerk or Auth0, connect your own domain for the redirect URI.

Client authentication can be client_secret_basic, client_secret_post, private_key_jwt (ES256 or RS256) or none for public clients. Client IDs start with upv_ and secrets with upvs_.

Claims

ClaimMeaning
subA stable ID just for your site, 22 characters. Web and mobile clients of the same startup on the same domain see the same value. Another startup sees the same person under a different value.
upvera:humanThe proof-of-personhood tier and the month it was reached, for example { "tier": "device_key", "since": "2026-05" }. Weigh it by your own rule: unsigned < web_key < device_key < device_attested.
name · pictureWith the profile scope, if the person allows.
email · email_verifiedWith the email scope, if the person turns it on. Off by default.
upvera:founderWith the upvera:founder scope: true if the person is a verified founder and allows it.

Example in Node

Using openid-client:

js
import * as client from 'openid-client';

// 1) discovery + client authentication (registered method: client_secret_basic)
const config = await client.discovery(
  new URL('https://id.upvera.co'),
  process.env.UPVERA_CLIENT_ID,      // upv_…
  undefined,
  client.ClientSecretBasic(process.env.UPVERA_CLIENT_SECRET), // upvs_…
);

// 2) sign-in: PKCE S256 + nonce + state (keep them in the session)
const code_verifier = client.randomPKCECodeVerifier();
const nonce = client.randomNonce();
const state = client.randomState();
const url = client.buildAuthorizationUrl(config, {
  redirect_uri: 'https://example.com/auth/upvera/callback',
  scope: 'openid profile',
  code_challenge: await client.calculatePKCECodeChallenge(code_verifier),
  code_challenge_method: 'S256',
  nonce,
  state,
});

// 3) callback: iss (RFC 9207), state and nonce are checked, the ID token is verified against the JWKS
const tokens = await client.authorizationCodeGrant(config, new URL(req.url, 'https://example.com'), {
  pkceCodeVerifier: code_verifier,
  expectedNonce: nonce,
  expectedState: state,
});
const claims = tokens.claims();
// claims.sub               → an ID just for this site
// claims['upvera:human']   → { tier: 'web_key' | 'device_key' | 'device_attested' | 'unsigned', since: '2026-05' }

Limits

/authorize accepts 30 requests per minute per IP and /token 60. Codes live 60 seconds, ID tokens 10 minutes.

Privacy

Upvera keeps which products a person signs in to only as a day bucket and a hash, for 400 days. The person can disconnect at any time. Public counts under 5 people aren't shown.

Questions and security reports: destek@upvera.co

Updated 2026-10-19