Toni integration guide

One authorization-code flow for new and returning users. Toni can reuse existing checks that satisfy your app's requirements; otherwise it requests verification. Your application installs the button and implements the callback; creating an API credential does not install either automatically.

1. Set up your workspace and website

  1. Sign up as a Partner or Developer, purchase the initial $5 in verification credit, and complete company details and representative identity verification.
  2. In API Credentials, add your website. Publish the supplied public meta tag in the homepage HTML head, or its exact token at /.well-known/toni-verification.txt. Select Verify connection. No registrar login or DNS edit is required.
  3. Select that connected website when creating live credentials. Register each exact HTTPS callback URL on the connected host or its www alias. Other subdomains require their own connection.
  4. Choose the verification checks and age requirement for the app. The workspace funds additional checks required by its users.

A successful website claim cannot belong to another workspace. A pending entry does not reserve the domain. Contact the workspace owner or Toni support for an existing claim; do not create an unrelated replacement domain.

Account active means account access is enabled, not that company review is complete. Company review pending means company details have not yet been independently verified. Representative identity and website-control checks remain separate requirements for new live credentials.

Existing integrations retain their credentials and current callback hosts. Connect a website to associate it with an existing app; use a separate app for a different host. Native app package or store ownership is not attested by this website flow.

2. Start authorization on your server

Create a fresh state, nonce, and PKCE verifier for each attempt. Store them with the intended local user or session in your server-side session, using a secure HttpOnly cookie. Redirect the browser to Toni.

import { randomBytes, createHash } from "node:crypto";

const state = randomBytes(32).toString("base64url");
const nonce = randomBytes(32).toString("base64url");
const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");
// Persist state, nonce, verifier and redirectUri in your server session.
const url = new URL("https://www.vbtoni.com/oauth/authorize");
url.search = new URLSearchParams({
  client_id: process.env.TONI_CLIENT_ID,
  redirect_uri: redirectUri, // Exact registered callback
  response_type: "code",
  scope: "openid id_verification",
  state,
  nonce,
  code_challenge: challenge,
  code_challenge_method: "S256"
}).toString();
// Respond with a redirect to url.toString().

The fragments here must be connected to your own session, error-handling, and account code. Add email or profile scopes only if needed and disclosed. Users see requested access before consenting.

3. Handle the callback and exchange the code

Handle an error response without signing the user in. Validate and consume the returned state against the pending session before exchanging the single-use code. Reject missing or mismatched state.

const response = await fetch("https://www.vbtoni.com/api/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    grant_type: "authorization_code",
    client_id: process.env.TONI_CLIENT_ID,
    client_secret: process.env.TONI_CLIENT_SECRET,
    code,
    redirect_uri: redirectUri,
    code_verifier: savedVerifier
  })
});
if (!response.ok) throw new Error("Toni token exchange failed");
const tokens = await response.json();

Keep secrets and token exchange on your backend. Do not log codes, tokens, or secrets. The response includes access_token, id_token, token_type, expires_in, and scoped user information. A callback alone is not proof of verification.

4. Validate the signed result

Use Toni's discovery document to configure the trusted issuer and signing-key URL. Pin that configuration to Toni, not to a URL supplied by an incoming token.

import { createRemoteJWKSet, jwtVerify } from "jose";

// Configure from Toni's discovery document, not untrusted token input.
const keys = createRemoteJWKSet(new URL(trustedJwksUri));
const { payload } = await jwtVerify(tokens.id_token, keys, {
  issuer: trustedIssuer,
  audience: process.env.TONI_CLIENT_ID,
  algorithms: ["RS256"]
});
if (payload.type !== "id_token" || payload.nonce !== savedNonce) {
  throw new Error("Invalid Toni identity token");
}
if (payload.verification_status !== "VERIFIED") {
  throw new Error("Verification required");
}
// Evaluate payload.verification against your app requirements.
// Link payload.sub to your local account, then establish its session.
  • sub is a partner-app-scoped identifier. Use it as the account link rather than assuming the same subject across applications.
  • id_verification permits verification_status and relevant check results, timestamps, and requested age-threshold results when available.
  • email permits email information; profile permits the available name. Do not assume optional fields exist.
  • Raw ID photos, face images, professional-license status, and universal compliance clearance are not part of this integration contract.

Validate expiration, issuer, audience, signature, nonce, and required results before granting access. Keep local access and revocation handling consistent with your policies; do not treat a saved token as permanent clearance.

5. Test before launch

New test credentials are restricted to localhost callbacks and cannot start paid identity checks. Test denied consent, invalid state, expired or reused codes, mismatched callbacks, insufficient coverage, and revoked access as well as the successful path.

Then test your registered live website with a real payment and verification, and confirm the itemized charge in Billing. A test credential or a successful redirect alone does not validate production billing.

The supported result path is the authorization callback and token exchange. There is no published partner webhook subscription API or Toni Node SDK webhook helper. Didit-to-Toni and Stripe-to-Toni webhooks are internal provider integrations.

toni