Serverless Telegram contact relay with channel-membership gating, structured intake, anti-spam filters, rate limits, and two-way administrator replies. Runs on the Cloudflare Workers free tier with Workers KV.
Publishing a contact bot often creates a second problem: unsolicited ads, bulk messages, and low-quality requests are forwarded straight to the operator. This project places a configurable gate in front of the relay:
flowchart LR
U[Telegram user] --> M{Channel member?}
M -- No --> J[Show join and recheck buttons]
M -- Yes --> C[Choose contact category]
C --> S{Spam and rate checks}
S -- Rejected --> B[Strike or blacklist]
S -- Accepted --> A[Copy message to admin]
A --> R[Admin replies to copied message]
R --> U
- Requires membership in a configured Telegram channel
- Inline category selection before a request can be submitted
- Rejects links and
@mentionsin the first request - Keyword, forwarded-message, repeated-character, and URL-count filters
- Per-user hourly rate limit
- Automatic blacklist after repeated violations
- Manual
/block,/unblock, and/statsadministrator commands - Copies text, photos, documents, and other supported Telegram messages
- Admin replies directly to the copied message to answer the user
- Webhook secret validation and Telegram update de-duplication
- No server process, polling loop, or always-on VM required
- A Telegram bot created with @BotFather
- A public Telegram channel; the bot must be an administrator in that channel
- A free Cloudflare account
- A Workers KV namespace
- Open Workers & Pages in the Cloudflare dashboard.
- Create a Worker named
telegram-contact-gate-bot. - Open Edit code.
- Replace the example with
src/index.js. - Save and deploy.
The health endpoint should return:
https://YOUR_WORKER.workers.dev/health
{"ok":true,"service":"telegram-contact-gate-bot"}Create a KV namespace and attach it to the Worker with this exact binding name:
BOT_DATA
| Secret | Description |
|---|---|
BOT_TOKEN |
Token created by BotFather |
WEBHOOK_SECRET |
Random alphanumeric webhook secret |
ADMIN_CHAT_ID |
Operator's numeric private-chat ID; use 0 temporarily |
Never commit these values.
| Variable | Example | Required |
|---|---|---|
BRAND_NAME |
Example Team |
No |
CHANNEL_USERNAME |
@your_channel |
Yes |
CHANNEL_URL |
https://t.me/your_channel |
Yes |
GROUP_URL |
https://t.me/your_group |
No |
WEBSITE_URL |
https://example.com |
No |
On Windows, from the repository directory:
Set-ExecutionPolicy -Scope Process Bypass
.\tools\setup-webhook.ps1Enter the base Worker URL without /health or /webhook. The script appends /webhook, configures Telegram commands, and prints getWebhookInfo.
Set ADMIN_CHAT_ID=0, deploy, configure the webhook, then send:
/myid
Update ADMIN_CHAT_ID with the returned number and deploy again.
When a user submits a valid request, the bot sends a metadata header and copies the user's message to the configured administrator chat. Reply directly to the copied user message; the bot copies the reply back to that user.
Commands:
/block USER_ID reason
/unblock USER_ID
/stats
/help
- Contact categories: edit
CATEGORIESnear the top ofsrc/index.js. - Spam rules: edit
SPAM_PATTERNS. - Hourly message allowance: edit
RATE_LIMIT_PER_HOUR. - Automatic blocking threshold: edit
BLOCK_AFTER_STRIKES. - User-facing strings are currently Chinese-first and can be translated directly in
src/index.js.
Node.js 20 or newer is sufficient; no production dependency is needed for tests.
npm test
npm run check- Treat the bot token like a password.
- Store
BOT_TOKEN,WEBHOOK_SECRET, andADMIN_CHAT_IDas Cloudflare encrypted secrets. - The bot intentionally fails closed when membership verification is unavailable.
- A Telegram bot has one active webhook. Connecting the same bot to another platform replaces this deployment's webhook.
- Review and customize spam patterns for your community before production use.
Please report vulnerabilities according to SECURITY.md, not through public issues.