Skip to content

Authentication

When Telegram opens your Mini App, it passes signed initData to the WebView. TMA.sh validates this data server-side and returns a JWT you can use to authenticate requests to your own backend or third-party services like Supabase.

  1. Telegram opens your Mini App and injects initData into the WebView
  2. Your app sends initData to the TMA.sh auth endpoint via the SDK
  3. TMA.sh validates the signature against your bot token
  4. A signed JWT is returned containing the Telegram user’s identity

Use the SDK to validate initData and get a JWT:

import { createTMA } from '@tma.sh/sdk';
const tma = createTMA({ projectId: 'your-project-id' });
const { user, jwt } = await tma.auth.validate(
window.Telegram.WebApp.initData,
'your-project-id'
);
console.log(user.telegramId); // 123456789
console.log(user.firstName); // "Alice"
console.log(user.username); // "alice"

The returned jwt is a signed token you can attach to subsequent requests:

const response = await fetch('https://myapp--api.tma.sh/api/profile', {
headers: {
Authorization: `Bearer ${jwt}`,
},
});

The JWT issued by TMA.sh contains the following claims:

ClaimDescriptionExample
subUnique subject identifiertg_123456789
telegramIdTelegram user ID123456789
firstNameUser’s first nameAlice
lastNameUser’s last name (may be empty)Smith
usernameTelegram username (may be empty)alice
projectIdYour TMA.sh project IDproj_abc123
iatIssued at (Unix timestamp)1700000000
expExpires at (24 hours after issuance)1700086400

Protect your API routes with the requireUser() middleware:

import { Hono } from 'hono';
import { requireUser } from '@tma.sh/sdk/server';
const app = new Hono();
app.use('/api/protected/*', requireUser());
app.get('/api/protected/profile', (c) => {
const user = c.get('user');
return c.json({
telegramId: user.telegramId,
username: user.username,
});
});
export default app;

The middleware verifies the JWT signature, checks expiration, and attaches the decoded user to the request context. Unauthorized requests receive a 401 response.

You can use the TMA.sh JWT directly with Supabase by configuring a custom JWT secret.

Step 1: In the TMA.sh dashboard, go to your project’s Settings and copy the JWT signing secret.

Step 2: In your Supabase dashboard, go to Settings > API and set the JWT secret to the same value.

Step 3: Pass the JWT to the Supabase client:

import { createClient } from '@supabase/supabase-js';
import { createTMA } from '@tma.sh/sdk';
const tma = createTMA({ projectId: 'your-project-id' });
const { jwt } = await tma.auth.validate(
window.Telegram.WebApp.initData,
'your-project-id'
);
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key',
{
global: {
headers: { Authorization: `Bearer ${jwt}` },
},
}
);
// Supabase RLS policies now see the Telegram user as `auth.jwt().sub`
const { data } = await supabase
.from('profiles')
.select('*')
.eq('telegram_id', 'tg_123456789');

This lets you use Supabase Row Level Security (RLS) with Telegram identities without managing a separate user table.

For custom backend verification, TMA.sh exposes a JWKS (JSON Web Key Set) endpoint:

https://api.tma.sh/.well-known/jwks.json

This is a global endpoint (not per-project). Use it to verify JWTs in any language or framework that supports JWKS:

import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://api.tma.sh/.well-known/jwks.json')
);
const { payload } = await jwtVerify(token, JWKS);
// payload.sub === 'tg_123456789'
import { TMAProvider, useTelegramAuth } from '@tma.sh/sdk/react';
function App() {
return (
<TMAProvider config={{ projectId: 'your-project-id' }}>
<Profile />
</TMAProvider>
);
}
function Profile() {
const { user, jwt, isLoading, error } = useTelegramAuth();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Welcome, {user.firstName}!</div>;
}
<script>
import { initTMAProvider, getTMAAuth } from '@tma.sh/sdk/svelte';
// Call once in root layout
initTMAProvider({ projectId: 'your-project-id' });
// Get the auth store
const auth = getTMAAuth();
</script>
{#if $auth.isLoading}
<p>Loading...</p>
{:else if $auth.user}
<p>Welcome, {$auth.user.firstName}!</p>
{/if}
  • JWTs expire after 24 hours. Re-validate initData to get a fresh token.
  • Never expose your bot token or JWT signing secret in client-side code.
  • Always verify JWTs server-side before trusting user identity.
  • Use HTTPS for all API requests (TMA.sh enforces this by default).