Bototeka OAuth

Add sign-in in one evening

Standard OpenID Connect, hosted registration and sign-in, customer management and transparent usage pricing.

openid-configurationLive
{
  "issuer": "https://bototeka.com/api",
  "authorization_endpoint": "https://bototeka.com/api/oauth/authorize",
  "token_endpoint": "https://bototeka.com/api/oauth/token",
  "userinfo_endpoint": "https://bototeka.com/api/oauth/userinfo",
  "scopes_supported": ["openid", "email", "profile", "offline_access"]
}

Quick start

Bototeka works with any OpenID Connect library that supports Authorization Code and PKCE S256.

  1. Create an application

    Xidmət adını, callback ünvanını və istəyə görə loqonu daxil edin. Google, Apple və Yandex callback-lərini Bototeka idarə edir.

  2. Save credentials

    Copy the Client ID and Client Secret. The secret is shown only once.

  3. Configure OIDC

    Give your library the issuer. It discovers every other endpoint automatically.

  4. Test the flow

    Start sign-in, validate state and nonce, exchange the code and create your local session.

Issuer
https://bototeka.com/api
Discovery URL
https://bototeka.com/api/.well-known/openid-configuration
Service API
https://api.bototeka.com/api/identity/v1
Redirect URI example
https://service.example/auth/callback

Sign-in and tokens

Your backend creates PKCE, state and nonce, sends the user to Bototeka and receives only a short-lived authorization code.

Create the authorization URL
import { createHash, randomBytes } from "node:crypto";

const clientId = process.env.BOTOTEKA_CLIENT_ID;
if (!clientId) throw new Error("BOTOTEKA_CLIENT_ID is required");

const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");
const state = randomBytes(24).toString("base64url");
const nonce = randomBytes(24).toString("base64url");

const authorize = new URL("https://bototeka.com/api/oauth/authorize");
authorize.search = new URLSearchParams({
  client_id: clientId,
  redirect_uri: "https://service.example/auth/callback",
  response_type: "code",
  scope: "openid email profile",
  code_challenge: challenge,
  code_challenge_method: "S256",
  state,
  nonce,
}).toString();

// Save verifier, state and nonce in the user's server-side session.
console.log(authorize.toString());
Exchange code for tokens
curl --request POST 'https://bototeka.com/api/oauth/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'client_id=YOUR_CLIENT_ID' \
  --data-urlencode 'client_secret=YOUR_CLIENT_SECRET' \
  --data-urlencode 'code=CODE_FROM_CALLBACK' \
  --data-urlencode 'redirect_uri=https://service.example/auth/callback' \
  --data-urlencode 'code_verifier=SAVED_PKCE_VERIFIER'
Fetch the profile
curl 'https://bototeka.com/api/oauth/userinfo' \
  --header 'Authorization: Bearer ACCESS_TOKEN'
After the callback: compare state, exchange the code once, verify the ID Token signature through JWKS, then validate issuer, audience, expiry and nonce. Store users by iss + sub, never by email.

Registration data through the API

Create an API key in company settings and request customers or events for applications in your workspace.

GET/applications/{applicationId}/customers

Returns sub, verified email, name and activity timestamps.

GET/applications/{applicationId}/events

Returns registration or sign-in, amount, currency, status and event time.

List application customers
curl 'https://api.bototeka.com/api/identity/v1/applications/APPLICATION_ID/customers?limit=50' \
  --header 'Authorization: Bearer btk_v1_YOUR_API_KEY'
List registration and sign-in events
curl 'https://api.bototeka.com/api/identity/v1/applications/APPLICATION_ID/events?limit=50' \
  --header 'Authorization: Bearer btk_v1_YOUR_API_KEY'

Reading requires identity:read. Send the key as a Bearer token and keep it on your server.

Webhooks

Subscribe in company settings. The endpoint must use HTTPS and the signing secret is shown only once.

identity.registration.completed.v1

Registration completed and committed to the usage ledger.

identity.login.completed.v1

A returning sign-in completed and was committed to the usage ledger.

Delivery is at least once. Deduplicate by event ID, accept events in any order and return 2xx only after saving the event.

Verify a webhook signature
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyBototekaWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-bototeka-webhook-timestamp"];
  const received = headers["x-bototeka-webhook-signature"];
  if (!timestamp || !received) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = "v1=" + createHmac("sha256", secret)
    .update(timestamp + "." + rawBody)
    .digest("hex");
  const left = Buffer.from(expected);
  const right = Buffer.from(received);
  return left.length === right.length && timingSafeEqual(left, right);
}

Production checklist

  • Use an exact HTTPS Redirect URI and never accept a callback to an arbitrary destination.
  • Generate a new state, nonce and PKCE verifier for every attempt and keep them in a server-side session.
  • Never store Client Secrets, API keys, access tokens or refresh tokens in browser or mobile analytics.
  • Request offline_access only when your backend truly needs a long-running session.
  • Verify webhook signatures against the raw body and handle repeated delivery safely.

Errors and recovery

invalid_request

What happened

A required parameter is missing or invalid.

What to do

Check Redirect URI, PKCE, state, nonce and Content-Type.

invalid_grant

What happened

The code expired, was used or does not match the verifier.

What to do

Start a new sign-in. Never retry the same code exchange.

temporarily_unavailable

What happened

The application balance is too low.

What to do

Top up the OAuth balance and start a new sign-in.

429

What happened

A request or verification-attempt limit was exceeded.

What to do

Honor Retry-After and retry with jittered backoff.

Knowledge base

When is usage charged?

After successful token issuance. A customer's first session is a registration and later sessions are sign-ins. Failed or cancelled attempts, refresh, UserInfo and logout are free.

Can I use email only?

Yes. Bototeka supports email registration, one-time codes, address verification and password recovery without social providers.

Can I connect a SPA?

Yes, as a public client with PKCE and a short-lived access token in memory. For production, a backend or BFF with an HttpOnly session is preferred.

What should I store as the customer ID?

Store the issuer and sub pair. Email may change and is not a stable identifier.

How do I rotate a secret?

Create or rotate the API key in company settings. Copy the new secret immediately, update server configuration and revoke old access.

Ready to connect

Create an application, save the secret and use the issuer from this guide.

Open OAuth settings