The webhookCallback function creates a callback that integrates your grammY bot with web frameworks for webhook-based deployments. Instead of long polling, webhooks allow Telegram to push updates directly to your server.
Function Signature
function webhookCallback<C extends Context = Context>(
bot: Bot<C>,
adapter: FrameworkAdapter | string,
webhookOptions?: WebhookOptions
): RequestHandler
adapter
FrameworkAdapter | string
required
Framework adapter name or custom adapter. Built-in adapters:
'express' - Express.js
'koa' - Koa
'fastify' - Fastify
'std/http' - Deno std/http
'hono' - Hono
'oak' - Oak (Deno)
'worktop' - Worktop (Cloudflare Workers)
'cloudflare' - Cloudflare Workers
'aws-lambda' - AWS Lambda
'azure' - Azure Functions
'vercel' - Vercel
Optional configuration for webhook behavior
Returns: Framework-specific request handler function
WebhookOptions
Configuration options for webhook handling.
onTimeout
'throw' | 'return' | ((...args: any[]) => unknown)
Strategy for handling request timeouts:
'throw' (default) - Throw an error on timeout
'return' - Return successfully on timeout
- Function - Custom timeout handler
Request timeout in milliseconds. Defaults to 10000 (10 seconds).
Secret token for validating requests via the X-Telegram-Bot-Api-Secret-Token header. Should match the token you set when configuring the webhook.
Basic Usage
Express
import express from 'express'
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
bot.command('start', (ctx) => ctx.reply('Hello!'))
const app = express()
app.use(express.json())
app.use(webhookCallback(bot, 'express'))
app.listen(3000, () => {
console.log('Webhook server running on port 3000')
})
Koa
import Koa from 'koa'
import { bodyParser } from '@koa/bodyparser'
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
bot.command('start', (ctx) => ctx.reply('Hello!'))
const app = new Koa()
app.use(bodyParser())
app.use(webhookCallback(bot, 'koa'))
app.listen(3000)
Fastify
import Fastify from 'fastify'
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
bot.command('start', (ctx) => ctx.reply('Hello!'))
const server = Fastify()
server.post('/', webhookCallback(bot, 'fastify'))
await server.listen({ port: 3000 })
Deno (std/http)
import { serve } from 'https://deno.land/std/http/server.ts'
import { Bot, webhookCallback } from 'https://deno.land/x/grammy/mod.ts'
const bot = new Bot('YOUR_BOT_TOKEN')
bot.command('start', (ctx) => ctx.reply('Hello!'))
const handleUpdate = webhookCallback(bot, 'std/http')
await serve(handleUpdate, { port: 3000 })
Hono
import { Hono } from 'hono'
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
bot.command('start', (ctx) => ctx.reply('Hello!'))
const app = new Hono()
app.post('/', webhookCallback(bot, 'hono'))
export default app
Advanced Configuration
With Secret Token
import express from 'express'
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
const SECRET_TOKEN = 'my-secret-token'
bot.command('start', (ctx) => ctx.reply('Secure webhook!'))
const app = express()
app.use(express.json())
app.use(webhookCallback(bot, 'express', {
secretToken: SECRET_TOKEN
}))
app.listen(3000)
// Set webhook with secret token
await bot.api.setWebhook('https://example.com/webhook', {
secret_token: SECRET_TOKEN
})
Custom Timeout Handling
import express from 'express'
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
const app = express()
app.use(express.json())
app.use(webhookCallback(bot, 'express', {
timeoutMilliseconds: 5000, // 5 second timeout
onTimeout: () => {
console.log('Request timed out, but continuing...')
}
}))
app.listen(3000)
Different Paths
import express from 'express'
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
const app = express()
app.use(express.json())
// Handle webhook at /webhook endpoint
app.post('/webhook', webhookCallback(bot, 'express'))
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok' })
})
app.listen(3000)
Vercel
// api/webhook.ts
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot(process.env.BOT_TOKEN!)
bot.command('start', (ctx) => ctx.reply('Hello from Vercel!'))
export default webhookCallback(bot, 'std/http')
Cloudflare Workers
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
bot.command('start', (ctx) => ctx.reply('Hello from Cloudflare!'))
export default {
async fetch(request: Request) {
const handler = webhookCallback(bot, 'cloudflare')
return await handler(request)
}
}
AWS Lambda
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot(process.env.BOT_TOKEN!)
bot.command('start', (ctx) => ctx.reply('Hello from Lambda!'))
export const handler = webhookCallback(bot, 'aws-lambda')
Azure Functions
import { AzureFunction, Context, HttpRequest } from '@azure/functions'
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot(process.env.BOT_TOKEN!)
bot.command('start', (ctx) => ctx.reply('Hello from Azure!'))
const httpTrigger: AzureFunction = webhookCallback(bot, 'azure')
export default httpTrigger
Setting Up the Webhook
After deploying your webhook server, configure Telegram to send updates to it:
import { Bot } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
// Set the webhook URL
await bot.api.setWebhook('https://example.com/webhook', {
allowed_updates: ['message', 'callback_query'],
secret_token: 'my-secret-token',
drop_pending_updates: true
})
console.log('Webhook configured!')
Webhook Best Practices
1. Use HTTPS
Telegram requires HTTPS for webhooks (except localhost for testing).
2. Validate Requests
Always use a secret token to validate incoming requests:
app.use(webhookCallback(bot, 'express', {
secretToken: process.env.SECRET_TOKEN
}))
3. Handle Timeouts
Telegram expects responses within 60 seconds. For long-running operations, acknowledge quickly:
bot.on('message', async (ctx) => {
// Start long operation without awaiting
processLongTask(ctx).catch(console.error)
// Respond quickly
await ctx.reply('Processing your request...')
})
4. Error Handling
Implement proper error handling:
bot.catch((err) => {
const ctx = err.ctx
console.error(`Error while handling update ${ctx.update.update_id}:`)
const e = err.error
if (e instanceof GrammyError) {
console.error('Error in request:', e.description)
} else {
console.error('Unknown error:', e)
}
})
5. Monitor Health
Add health check endpoints:
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: Date.now(),
botUsername: bot.botInfo?.username
})
})
Debugging Webhooks
Check Webhook Info
const info = await bot.api.getWebhookInfo()
console.log('Webhook URL:', info.url)
console.log('Pending updates:', info.pending_update_count)
console.log('Last error:', info.last_error_message)
Delete Webhook
await bot.api.deleteWebhook({ drop_pending_updates: true })
console.log('Webhook deleted')
Test Locally with ngrok
# Start your local server
node server.js
# In another terminal, expose it via ngrok
ngrok http 3000
# Use the ngrok URL to set your webhook
await bot.api.setWebhook('https://abc123.ngrok.io/webhook')
Complete Example
import express from 'express'
import { Bot, webhookCallback, GrammyError, HttpError } from 'grammy'
const bot = new Bot(process.env.BOT_TOKEN!)
const SECRET_TOKEN = process.env.SECRET_TOKEN!
const WEBHOOK_URL = process.env.WEBHOOK_URL!
const PORT = process.env.PORT || 3000
// Bot handlers
bot.command('start', (ctx) => {
ctx.reply('Welcome! I\'m running on webhooks.')
})
bot.on('message:text', (ctx) => {
ctx.reply(`You said: ${ctx.message.text}`)
})
// Error handling
bot.catch((err) => {
const ctx = err.ctx
console.error(`Error while handling update ${ctx.update.update_id}:`)
const e = err.error
if (e instanceof GrammyError) {
console.error('Error in request:', e.description)
} else if (e instanceof HttpError) {
console.error('Could not contact Telegram:', e)
} else {
console.error('Unknown error:', e)
}
})
// Express server
const app = express()
app.use(express.json())
// Webhook endpoint
app.post('/webhook', webhookCallback(bot, 'express', {
secretToken: SECRET_TOKEN,
timeoutMilliseconds: 10000,
onTimeout: 'return'
}))
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: Date.now() })
})
// Start server and configure webhook
app.listen(PORT, async () => {
console.log(`Server running on port ${PORT}`)
// Set webhook
await bot.api.setWebhook(WEBHOOK_URL, {
secret_token: SECRET_TOKEN,
allowed_updates: ['message', 'callback_query']
})
const info = await bot.api.getWebhookInfo()
console.log('Webhook configured:', info.url)
})
// Graceful shutdown
process.once('SIGINT', async () => {
console.log('Deleting webhook...')
await bot.api.deleteWebhook()
process.exit(0)
})
process.once('SIGTERM', async () => {
console.log('Deleting webhook...')
await bot.api.deleteWebhook()
process.exit(0)
})
See Also