A Cloudflare Worker for forwarding messages to Telegram chats using the Telegram Bot API. This service provides a REST API for sending messages to single or multiple Telegram chats, with support for reusable profile configurations.
- Message Forwarding: Send messages to one or multiple Telegram chats via REST API
- Profile Management: Store and reuse bot token and chat ID combinations
- Batch Sending: Send the same message to multiple chats in a single request
- Flexible Content: Support for plain text and rich formatting (HTML, Markdown)
- Multi-Status Responses: HTTP 207 for partial batch failures with detailed per-chat results
- API Authentication: Bearer token authentication to protect all endpoints
- Security: Bot tokens excluded from all response bodies, constant-time token comparison
- Database Storage: Cloudflare D1 database for persistent profile storage
- CORS Support: Cross-origin requests enabled for browser access
Before you begin, you'll need:
- Cloudflare Account: Sign up at cloudflare.com
- Telegram Bot: Create a bot via @BotFather and get your bot token
- Chat IDs: Obtain the chat IDs where you want to send messages
- For personal chats: Use your user ID (get it from @userinfobot)
- For groups: Add your bot to the group and get the group chat ID (negative number)
- For channels: Add your bot as admin and get the channel ID
- Node.js: Version 16 or higher
- Wrangler CLI: Cloudflare's CLI tool for Workers (installed via npm)
- Clone the repository (or copy the files to your project):
git clone <repository-url>
cd telegram-worker- Install dependencies:
npm install- Create a D1 database for local development:
wrangler d1 create telegram_worker_dbTake note of the database_id from the output and update wrangler.toml.
- Update wrangler.toml with your database ID:
[[d1_databases]]
binding = "DB"
database_name = "telegram_worker_db"
database_id = "your-database-id-here"- Apply the database schema:
wrangler d1 execute telegram_worker_db --local --file=./schema.sql- Set up environment variables for local development:
cp .env.example .dev.vars
# Edit .dev.vars and set API_TOKEN to any test valueIMPORTANT: All API endpoints (except OPTIONS for CORS) require authentication via Bearer token.
- Generate a secure API token:
# Generate a random 32-character token (recommended)
openssl rand -base64 32
# Or use any strong password generator- Set the API token as a Cloudflare secret:
wrangler secret put API_TOKEN
# Enter your token when prompted- Include the token in all API requests:
curl -X POST https://your-worker.workers.dev/send-message \
-H "Authorization: Bearer your-api-token-here" \
-H "Content-Type: application/json" \
-d '{"chatId": "123456789", "botToken": "...", "content": "Hello!"}'Security Notes:
- Use a strong, random token (at least 32 characters recommended)
- Never commit tokens to version control
- Rotate tokens regularly for security
- Use different tokens for different environments (dev, staging, production)
- All authentication failures return the same generic 401 error (no details leaked)
For local development, copy the example environment file:
cp .env.example .dev.varsEdit .dev.vars and set the required variables:
API_TOKEN- Required for authentication (use any test value locally)TELEGRAM_BOT_TOKEN- Optional default bot tokenFORWARD_TO_CHAT_ID- Optional default chat ID
Example .dev.vars for local testing:
API_TOKEN=test-token-dev-12345
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz12345678
FORWARD_TO_CHAT_ID=123456789For production deployment, use Cloudflare Secrets for sensitive values:
# Required
wrangler secret put API_TOKEN
# Optional
wrangler secret put TELEGRAM_BOT_TOKENNote: Bot tokens and chat IDs can be provided per-request or stored in profiles. Environment variables are optional defaults.
The worker uses a single profiles table to store reusable bot configurations:
CREATE TABLE profiles (
id TEXT PRIMARY KEY,
chat_ids TEXT NOT NULL, -- JSON array of chat IDs
bot_token TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);Run the worker locally with hot reloading:
npm run devThis starts a local server at http://localhost:8787 with access to your local D1 database.
POST /send-message
Send a message to one or multiple Telegram chats.
Request Body (Direct Mode):
{
"chatId": "123456789",
"botToken": "123456:ABC-DEF...",
"content": "Hello, World!"
}Request Body (Profile Mode):
{
"profileId": "my-profile",
"content": {
"text": "<b>Alert!</b>",
"parse_mode": "HTML"
}
}Request Body (Batch Mode):
{
"chatId": ["123456789", "-987654321"],
"botToken": "123456:ABC-DEF...",
"content": "Broadcast message"
}Response (200 OK - All messages sent):
{
"success": true,
"data": {
"results": [
{
"chatId": "123456789",
"success": true,
"messageId": 42
}
],
"totalSent": 1,
"totalFailed": 0
}
}Response (207 Multi-Status - Partial failure):
{
"success": true,
"data": {
"results": [
{
"chatId": "123456789",
"success": true,
"messageId": 42
},
{
"chatId": "-987654321",
"success": false,
"error": "Forbidden: bot is not a member of the supergroup chat"
}
],
"totalSent": 1,
"totalFailed": 1
}
}POST /profiles
Create a new reusable profile with bot token and chat IDs.
Request Body:
{
"id": "production-bot",
"chatIds": ["123456789", "-987654321"],
"botToken": "123456:ABC-DEF..."
}Response (201 Created):
{
"success": true,
"data": {
"id": "production-bot",
"chat_ids": "[\"123456789\",\"-987654321\"]",
"created_at": 1699999999999,
"updated_at": 1699999999999
}
}GET /profiles/:id
Retrieve a specific profile by ID.
Response (200 OK):
{
"success": true,
"data": {
"id": "production-bot",
"chat_ids": "[\"123456789\",\"-987654321\"]",
"created_at": 1699999999999,
"updated_at": 1699999999999
}
}GET /profiles?limit=50&offset=0
List all profiles with pagination.
Query Parameters:
limit- Maximum number of profiles to return (default: 50, max: 1000)offset- Number of profiles to skip (default: 0)
Response (200 OK):
{
"success": true,
"data": [
{
"id": "profile-1",
"chat_ids": "[\"123456789\"]",
"created_at": 1699999999999,
"updated_at": 1699999999999
},
{
"id": "profile-2",
"chat_ids": "[\"111\",\"222\"]",
"created_at": 1699999999998,
"updated_at": 1699999999998
}
]
}PUT /profiles/:id
Update an existing profile's chat IDs and/or bot token.
Request Body (at least one field required):
{
"chatIds": ["111111111", "222222222"],
"botToken": "123456:NEW-TOKEN"
}Response (200 OK):
{
"success": true,
"data": {
"id": "production-bot",
"chat_ids": "[\"111111111\",\"222222222\"]",
"created_at": 1699999999999,
"updated_at": 1700000000000
}
}DELETE /profiles/:id
Delete a profile permanently.
Response (200 OK):
{
"success": true,
"data": {
"message": "Profile deleted successfully"
}
}Note: All examples below require the Authorization: Bearer <token> header. Replace YOUR_API_TOKEN with your actual API token.
curl -X POST https://your-worker.workers.dev/send-message \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"chatId": "123456789",
"botToken": "123456:ABC-DEF...",
"content": "Hello from Telegram Worker!"
}'curl -X POST https://your-worker.workers.dev/send-message \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"chatId": "123456789",
"botToken": "123456:ABC-DEF...",
"content": {
"text": "<b>Alert!</b>\nSomething important happened.",
"parse_mode": "HTML",
"disable_notification": false
}
}'curl -X POST https://your-worker.workers.dev/send-message \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"chatId": ["123456789", "-987654321", "555555555"],
"botToken": "123456:ABC-DEF...",
"content": "Broadcast message to all chats"
}'curl -X POST https://your-worker.workers.dev/profiles \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "my-bot",
"chatIds": ["123456789"],
"botToken": "123456:ABC-DEF..."
}'curl -X POST https://your-worker.workers.dev/send-message \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"profileId": "my-bot",
"content": "Message using stored profile!"
}'curl https://your-worker.workers.dev/profiles?limit=10&offset=0 \
-H "Authorization: Bearer YOUR_API_TOKEN"curl -X PUT https://your-worker.workers.dev/profiles/my-bot \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"chatIds": ["111111111", "222222222"]
}'curl -X DELETE https://your-worker.workers.dev/profiles/my-bot \
-H "Authorization: Bearer YOUR_API_TOKEN"-
Never commit bot tokens to version control
- Add
.envfiles to.gitignore - Use Cloudflare secrets for production tokens
- Add
-
Store tokens securely
- Use profiles to store tokens in the D1 database
- Tokens are never included in API responses
- Database is only accessible from your worker
-
Rotate tokens regularly
- Generate new tokens via @BotFather
- Update profiles with new tokens
- Revoke old tokens
The worker uses Bearer token authentication to protect all endpoints.
Authentication Security:
- All requests require valid API token in Authorization header
- Constant-time token comparison prevents timing attacks
- Generic error responses prevent information leakage
- OPTIONS requests bypass auth to allow CORS preflight
Additional Security Measures (optional):
-
Rate limiting
- Use Cloudflare Rate Limiting rules
- Implement per-profile rate limits
-
IP whitelisting
- Restrict access to known IPs via Cloudflare Firewall rules
- Combine with token auth for defense in depth
-
Token rotation
- Rotate API tokens regularly (e.g., every 90 days)
- Use different tokens per environment
- Revoke old tokens after rotation
- Chat IDs are validated as numeric strings
- Negative numbers indicate groups/channels
- Positive numbers indicate private chats
- Invalid IDs will fail with descriptive errors
The API uses standard HTTP status codes:
- 200 OK - Request successful
- 201 Created - Profile created successfully
- 207 Multi-Status - Partial success (some messages sent, some failed)
- 400 Bad Request - Invalid request body or parameters
- 401 Unauthorized - Missing or invalid API token
- 404 Not Found - Profile or route not found
- 405 Method Not Allowed - Invalid HTTP method for route
- 500 Internal Server Error - Server or Telegram API error
All error responses include a descriptive message:
{
"success": false,
"error": "Missing required field: chatId",
"details": "Additional error information (optional)"
}Authentication errors (401):
{
"success": false,
"error": "Unauthorized"
}See DEPLOYMENT.md for detailed production deployment instructions.
Quick deployment:
# Deploy to production
npm run deploySee API_REFERENCE.md for complete API documentation including:
- Detailed endpoint specifications
- Request/response schemas
- Error codes and messages
- Advanced usage examples
telegram-worker/
├── src/
│ ├── index.ts # Worker entry point and routing
│ ├── types.ts # TypeScript type definitions
│ ├── handlers/
│ │ ├── message.ts # /send-message endpoint handler
│ │ └── profile.ts # /profiles endpoint handlers
│ ├── services/
│ │ ├── telegram.ts # Telegram Bot API integration
│ │ └── database.ts # D1 database operations
│ └── utils/
│ └── validation.ts # Input validation functions
├── examples/ # Example cURL scripts
├── schema.sql # Database schema
├── wrangler.toml # Cloudflare Worker configuration
├── package.json # Node.js dependencies
├── tsconfig.json # TypeScript configuration
├── README.md # This file
├── docs/
│ ├── API_REFERENCE.md # Detailed API documentation
│ └── DEPLOYMENT.md # Deployment guide
# Start local development server
npm run dev
# Format code with Biome
npm run format
# Lint code with Biome
npm run lint
# Run both formatting and linting
npm run check
# Deploy to production
npm run deployThis error indicates authentication failure. All requests except OPTIONS must include a valid Bearer token.
Common causes:
-
Missing Authorization header
# Wrong - no Authorization header curl https://your-worker.workers.dev/profiles # Correct curl https://your-worker.workers.dev/profiles \ -H "Authorization: Bearer YOUR_API_TOKEN"
-
Incorrect token format
# Wrong - missing "Bearer " prefix curl -H "Authorization: YOUR_API_TOKEN" ... # Wrong - typo in "Bearer" curl -H "Authorization: Bearar YOUR_API_TOKEN" ... # Correct curl -H "Authorization: Bearer YOUR_API_TOKEN" ...
-
Invalid token value
- Verify you're using the correct token
- Check the token was set correctly:
wrangler secret list - Ensure there are no extra spaces or newlines in the token
- Try regenerating and setting a new token
-
Token not set in Cloudflare
# Set the API token wrangler secret put API_TOKEN
Security note: For security reasons, all authentication failures return the same generic "Unauthorized" message without details about what failed.
The user has blocked your bot. Ask them to unblock it or remove that chat ID from your profile.
Your bot needs to be added to the group/channel as a member (or admin for channels).
Check that your Telegram bot token is correct. Generate a new token via @BotFather if needed.
Verify the chat ID is correct. Make sure it's a string and includes the negative sign for groups.
Ensure your content field contains text. Empty strings are not allowed.
If you see D1 database errors:
- Verify the database exists:
wrangler d1 list - Check the schema is applied:
wrangler d1 execute telegram_worker_db --local --command "SELECT * FROM profiles LIMIT 1" - Verify
wrangler.tomlhas the correct database_id
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Submit a pull request
ISC License - see LICENSE file for details
For issues and questions:
- Open an issue on GitHub
- Check the API_REFERENCE.md for detailed documentation
- Review DEPLOYMENT.md for deployment help
- Built with Cloudflare Workers
- Uses Telegram Bot API
- Database powered by Cloudflare D1