- JavaScript 100%
| .claude | ||
| src | ||
| test | ||
| .gitignore | ||
| debug-e2e.mjs | ||
| DESIGN.md | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| wrangler.toml | ||
cloudflare-webhook-proxy
A Cloudflare Worker that receives webhook notifications (e.g. from Netdata, Grafana, Uptime Kuma) and forwards them to Telegram and/or Feishu (Lark) channels. Routes are stored in Workers KV and can be managed dynamically through a built-in admin API and UI — no redeployment needed when adding or changing destinations.
Features
- Fan-out: one incoming webhook path → multiple Telegram/Feishu destinations
- Per-route optional Bearer token authentication for incoming webhooks
- Admin UI at
/adminfor managing routes through a browser - REST API for programmatic route management
- All credentials stored securely in KV — no secrets in code
Architecture
POST /webhook/<path> → look up routes in KV → forward to Telegram / Feishu
GET /admin → admin UI (HTML)
/admin/routes → CRUD API (requires ADMIN_AUTH_TOKEN)
Prerequisites
- Node.js 18 or later
- A Cloudflare account (free tier is sufficient)
- A Telegram bot token — create one via @BotFather
- A Feishu custom bot webhook URL (if using Feishu)
Step-by-step deployment
1. Clone and install dependencies
git clone <your-repo-url>
cd cloudflare-webhook-proxy
npm install
2. Log in to Cloudflare
npx wrangler login
This opens a browser window to authorize Wrangler with your Cloudflare account.
3. Create the KV namespace
Routes are stored in a Workers KV namespace. Create two namespaces — one for production and one for preview (local dev):
npx wrangler kv namespace create ROUTES_KV
npx wrangler kv namespace create ROUTES_KV --preview
Each command prints output like:
{ binding = "ROUTES_KV", id = "abc123..." }
Copy the IDs and update wrangler.toml:
[[kv_namespaces]]
binding = "ROUTES_KV"
id = "YOUR_PRODUCTION_ID_HERE"
preview_id = "YOUR_PREVIEW_ID_HERE"
4. Set the admin token secret
Choose a strong random string for ADMIN_AUTH_TOKEN. This is the password for the admin UI and API.
npx wrangler secret put ADMIN_AUTH_TOKEN
When prompted, paste your chosen token and press Enter.
5. Run tests (optional but recommended)
npm test
All 66 tests should pass.
6. Deploy to Cloudflare
npm run deploy
On success, Wrangler prints your worker URL:
Published webhook-proxy (X.XXs)
https://webhook-proxy.<your-subdomain>.workers.dev
Initial setup: configure your first route
Using the Admin UI
-
Open
https://webhook-proxy.<your-subdomain>.workers.dev/adminin your browser -
Enter your
ADMIN_AUTH_TOKENand click Sign In -
Click + Add Route and fill in the form:
Field Description Webhook Path URL segment that identifies this source, e.g. netdataPlatform TelegramorFeishuTelegram Chat ID The chat/channel ID, e.g. -100123456789Bot Token Your Telegram bot token (Telegram only) Feishu Webhook URL The full webhook URL from Feishu (Feishu only) Source Auth Token Optional — callers must send this as Bearertoken -
Click Add Route. The route is live immediately.
Using the API directly
# List all routes
curl https://webhook-proxy.<your-subdomain>.workers.dev/admin/routes \
-H "Authorization: Bearer <ADMIN_AUTH_TOKEN>"
# Add a Telegram route
curl -X POST https://webhook-proxy.<your-subdomain>.workers.dev/admin/routes \
-H "Authorization: Bearer <ADMIN_AUTH_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"path": "netdata",
"platform": "telegram",
"channel_id": "-100123456789",
"bot_token": "123456:ABC-DEF...",
"auth_token": "my-webhook-secret"
}'
# Add a Feishu route for the same path (fan-out)
curl -X POST https://webhook-proxy.<your-subdomain>.workers.dev/admin/routes \
-H "Authorization: Bearer <ADMIN_AUTH_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"path": "netdata",
"platform": "feishu",
"channel_id": "https://open.feishu.cn/open-apis/bot/v2/hook/xxx",
"auth_token": "my-webhook-secret"
}'
# Delete a route
curl -X DELETE https://webhook-proxy.<your-subdomain>.workers.dev/admin/routes/<id> \
-H "Authorization: Bearer <ADMIN_AUTH_TOKEN>"
Sending a webhook
Point your monitoring tool to:
POST https://webhook-proxy.<your-subdomain>.workers.dev/webhook/<path>
Authorization: Bearer <auth_token> (if the route requires one)
Content-Type: application/json
Example payload (Netdata-style):
{
"title": "Disk Space Alert",
"level": "critical",
"host": "server1",
"message": "Disk /var usage is at 95%",
"value": "95%"
}
The worker accepts any JSON object. Recognized fields (title, level, status, host, message, info, description, timestamp) are formatted into a structured message. All other fields appear under "Details".
Example: Netdata configuration
In health_alarm_notify.conf, set:
SEND_CUSTOM="YES"
DEFAULT_RECIPIENT_CUSTOM="https://webhook-proxy.<your-subdomain>.workers.dev/webhook/netdata"
custom_sender() {
local webhook_url="${1}"
curl -s -X POST "${webhook_url}" \
-H "Authorization: Bearer my-webhook-secret" \
-H "Content-Type: application/json" \
-d "{\"title\":\"${alarm}\",\"level\":\"${status}\",\"host\":\"${host}\",\"message\":\"${info}\"}"
}
Local development
npm run dev
Wrangler starts a local server at http://localhost:8787 backed by a local KV store. Changes to source files are hot-reloaded.
Note: On first
devrun, Wrangler creates a local KV store automatically using thepreview_id. No extra setup is needed.
Admin API reference
All endpoints (except GET /admin) require Authorization: Bearer <ADMIN_AUTH_TOKEN>.
| Method | Path | Description |
|---|---|---|
GET |
/admin |
Admin UI (HTML page) |
GET |
/admin/routes |
List all routes |
POST |
/admin/routes |
Create a route |
GET |
/admin/routes/:id |
Get a single route |
PUT |
/admin/routes/:id |
Update a route |
DELETE |
/admin/routes/:id |
Delete a route |
Route object
{
"id": "uuid (auto-generated, immutable)",
"path": "url-segment",
"platform": "telegram | feishu",
"channel_id": "telegram chat id or feishu webhook url",
"bot_token": "telegram bot token (null for feishu)",
"auth_token": "required bearer token for callers (null = public)"
}
Updating the worker
After editing source files:
npm run deploy
Routes stored in KV are unaffected by redeployments.
Running a specific environment
npm run deploy -- --env production
npm run deploy -- --env staging
Remember to create separate KV namespaces for each environment and update wrangler.toml under [env.production] / [env.staging] accordingly.