Skip to main content
Bondify authentication is built around short-lived sessions and cryptographic proofs. Your frontend asks Bondify to open a Telegram conversation, your user taps a single button to confirm their identity, and Bondify hands your server a signed JWT that proves the user is who they claim to be — with no passwords, no SMS, and no shared secrets sent over the wire.

The Authentication Flow

1

Your frontend creates a session

When the user clicks “Sign in with Telegram”, the Bondify SDK calls POST /api/v1/generate/public with your project_id. Bondify creates a short-lived session and returns two values:
  • session_token — a unique token you will use to poll for the result.
  • deeplink — a https://t.me/... URL that opens your Bondify Telegram bot.
POST https://tgauth-backend.onrender.com/api/v1/generate/public
Content-Type: application/json

{ "project_id": "proj_xxxxxxxx" }
Response
{
  "session_token": "abc123xyz",
  "deeplink": "https://t.me/BondifyBot?start=abc123xyz"
}
2

Telegram opens and the bot sends a confirmation prompt

The SDK opens the deeplink in a new tab or redirects the user to the Telegram app. The Bondify bot immediately sends the user a message containing a ✅ Confirm button and a ❌ Cancel button, along with the name of your app so they know what they are authorising.
3

The user confirms or cancels

The user taps one of the two buttons in Telegram:
  • ✅ Confirm — Bondify marks the session as confirmed and signs a proof JWT.
  • ❌ Cancel — Bondify marks the session as cancelled.
No code entry, no password, no redirect dance. The entire interaction happens inside the Telegram app the user already has open.
4

Your frontend polls for the result

While the user is in Telegram, your app polls POST /api/v1/verify/public every 2 seconds with the session_token. The SDK handles this loop automatically — you only receive the final result in your onSuccess or onError callback.
POST https://tgauth-backend.onrender.com/api/v1/verify/public
Content-Type: application/json

{
  "project_id": "proj_xxxxxxxx",
  "session_token": "abc123xyz"
}
While waiting, the endpoint returns:
Pending response
{ "status": "pending" }
5

Bondify returns the confirmed user and a proof

When the user taps ✅ Confirm, the next poll returns the full result:
Confirmed response
{
  "status": "confirmed",
  "telegram_id": "123456789",
  "telegram_name": "Alex Johnson",
  "telegram_username": "alexj",
  "proof": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
The SDK surfaces this as a typed user object in your onSuccess callback:
onSuccess={(user) => {
  // user.telegramId   "123456789"
  // user.name         "Alex Johnson"
  // user.username     "alexj"
  // user.proof        "eyJ..."  ← send this to your server
}}
6

Your server verifies the proof and creates a session

Send the proof to your backend API route. Call verifyProof from @bondify/server — it checks the HMAC-SHA256 signature against your secret key. If valid, create your own session (cookie, JWT, database row) and return it to the client.
import { verifyProof } from '@bondify/server';

const user = await verifyProof(proof, process.env.BONDIFY_SECRET_KEY!);
// user.telegramId, user.name, user.username

Session Statuses

Every session moves through a defined lifecycle. Your polling code and webhook handler must handle all five statuses.
StatusMeaningWhat to do
pendingWaiting for user action in TelegramKeep polling every 2 s, show a loading spinner
confirmedUser tapped ✅ ConfirmStop polling, send proof to your server, log user in
cancelledUser tapped ❌ CancelStop polling, show “You declined the request”
expiredSession not used within 10 minutesStop polling, offer the user a “Try again” button
usedProof already consumed (verified once)Do not reuse — generate a fresh session
Sessions expire automatically after 10 minutes. If a user leaves Telegram open without tapping either button, the session transitions to expired. Always handle this state in your UI so users are never left on a frozen loading screen.

Cryptographic Proof

When a session reaches confirmed, Bondify signs a JWT using HMAC-SHA256 with your project’s secret key. This proof encodes the user’s telegram_id, name, and username, along with a timestamp and the session token. You must verify this proof on your server before creating a user session:
import { verifyProof } from '@bondify/server';

try {
  const user = await verifyProof(proof, process.env.BONDIFY_SECRET_KEY!);
  // Safe to issue a session — identity is cryptographically confirmed
} catch (err) {
  // Proof is invalid, tampered, or expired — reject the request
}
verifyProof throws if the signature is invalid, the proof has expired, or the payload is malformed. Never skip this step.
Never verify proofs client-side. Your BONDIFY_SECRET_KEY must remain server-only. A proof verified in the browser is trivially forgeable — an attacker who knows your project ID can craft a fake confirmation. Always route proof verification through your own backend API.

Webhook Alternative

Polling is the default and works in all environments, but if your backend needs to react to auth events without waiting for a client request, you can receive webhooks instead. Configure a webhook URL in your project settings and Bondify will POST a signed payload to your endpoint the moment a session changes state:
auth.confirmed event
{
  "event": "auth.confirmed",
  "session_token": "abc123xyz",
  "telegram_id": "123456789",
  "telegram_name": "Alex Johnson",
  "telegram_username": "alexj",
  "confirmed_at": 1700000000000
}
auth.cancelled event
{
  "event": "auth.cancelled",
  "session_token": "abc123xyz",
  "cancelled_at": 1700000000000
}
Every webhook request includes an X-Bondify-Signature header containing an HMAC-SHA256 signature of the raw request body. Always verify this signature before processing the event.
Verify webhook signature (Node.js)
import crypto from 'crypto';

const sig = req.headers['x-bondify-signature'];
const expected = crypto
  .createHmac('sha256', process.env.BONDIFY_SECRET_KEY!)
  .update(JSON.stringify(req.body))
  .digest('hex');

if (sig !== expected) {
  return res.status(401).end(); // Reject unsigned or tampered payloads
}
For the full webhook reference, including retry behaviour and event schemas, see the Webhooks guide.