Skip to main content
After a user completes Telegram sign-in in the browser or mobile app, your frontend receives a proof — a signed JWT issued by Bondify’s servers. Before you create a session or trust any of the user’s identity data, your backend must verify that proof cryptographically. The @bondify/server package exposes a single async function, verifyProof, that decodes and validates the token, throws on any failure, and returns a clean user object you can safely store or use to issue your own credentials.

Install

npm install @bondify/server

Environment variable

Store your secret key in an environment variable. Never hard-code it in source files.
.env
BONDIFY_SECRET_KEY=sk_live_...
The BONDIFY_SECRET_KEY must never be exposed to the client — not in browser JavaScript, not in a mobile app binary, and not in any public-facing API response. Treat it the same way you would a database password. If it leaks, anyone can forge proofs for arbitrary users.

Express example

The /api/session endpoint receives the proof from your frontend, verifies it, then issues your own JWT or sets a session cookie.
server.ts
import express from 'express';
import { verifyProof } from '@bondify/server';

const app = express();
app.use(express.json());

app.post('/api/session', async (req, res) => {
  try {
    const user = await verifyProof(
      req.body.proof,
      process.env.BONDIFY_SECRET_KEY
    );

    // user.telegramId — string  (the user's Telegram ID)
    // user.name       — string  (display name from Telegram)
    // user.username   — string | null  (Telegram @handle, may be absent)

    // Issue your own JWT or session token here:
    const token = await issueJWT(user);
    res.json({ token });
  } catch (err) {
    res.status(401).json({ error: 'Invalid proof' });
  }
});

app.listen(3000);

Fastify example

The same pattern works identically in Fastify:
server.ts
import Fastify from 'fastify';
import { verifyProof } from '@bondify/server';

const app = Fastify();

app.post('/api/session', async (request, reply) => {
  try {
    const user = await verifyProof(
      (request.body as any).proof,
      process.env.BONDIFY_SECRET_KEY
    );

    const token = await issueJWT(user);
    return { token };
  } catch {
    return reply.status(401).send({ error: 'Invalid proof' });
  }
});

app.listen({ port: 3000 });

verifyProof signature

function verifyProof(proof: string, secretKey: string): Promise<BondifyUser>
ParameterTypeDescription
proofstringThe JWT proof string received from the client
secretKeystringYour BONDIFY_SECRET_KEY from the dashboard

BondifyUser object

verifyProof resolves with a plain object containing the verified user’s identity:
FieldTypeDescription
telegramIdstringThe user’s unique Telegram ID
namestringThe user’s display name from Telegram
usernamestring | nullThe user’s Telegram @handle — null if they have none set
telegramId is the stable primary identifier for a user — it never changes even if the user updates their name or @handle. Use it as the foreign key when storing users in your database.

Error handling

verifyProof throws a BondifyError in all failure cases. Always wrap the call in try/catch and respond with 401 Unauthorized — never let an unverified proof result in a valid session.
import { verifyProof, BondifyError } from '@bondify/server';

try {
  const user = await verifyProof(proof, process.env.BONDIFY_SECRET_KEY);
  // ... create session
} catch (err) {
  if (err instanceof BondifyError) {
    // err.code is one of: 'INVALID_PROOF' | 'EXPIRED' | 'WRONG_PROJECT'
    console.error(`Auth failed [${err.code}]: ${err.message}`);
  }
  res.status(401).json({ error: 'Unauthorized' });
}
Common reasons verifyProof throws:
Error codeCause
INVALID_PROOFThe JWT signature does not match — possibly tampered or issued by a different key
EXPIREDThe proof was issued more than 5 minutes ago and is no longer valid
WRONG_PROJECTThe proof was issued for a different project ID

Upserting users in your database

A typical session creation flow looks up the user by telegramId and creates them if they don’t exist yet:
import { verifyProof } from '@bondify/server';
import { db } from './db'; // your database client

app.post('/api/session', async (req, res) => {
  try {
    const { telegramId, name, username } = await verifyProof(
      req.body.proof,
      process.env.BONDIFY_SECRET_KEY
    );

    const user = await db.user.upsert({
      where: { telegramId },
      update: { name, username },
      create: { telegramId, name, username },
    });

    const token = await issueJWT({ sub: user.id });
    res.json({ token });
  } catch {
    res.status(401).json({ error: 'Invalid proof' });
  }
});

Next steps

HTML Widget

The fastest way to collect the proof on a static HTML page — no npm install needed.

Next.js integration

Call verifyProof inside a Next.js Server Action and set a session cookie in one step.