No description
  • JavaScript 100%
Find a file
Yutong Li 7ceac4b31c
Merge pull request #2 from davidliyutong/claude/fix-webhook-json-error-E2hZJ
Fix Feishu signature algorithm and support form-encoded webhooks
2026-05-14 16:09:16 +08:00
src fix: correct Feishu signature algorithm (HMAC key vs message were swapped) 2026-05-14 08:07:59 +00:00
test fix: correct Feishu signature algorithm (HMAC key vs message were swapped) 2026-05-14 08:07:59 +00:00
.gitignore feat: add GitHub webhook forwarding to Feishu 2026-05-14 15:35:52 +08:00
AGENTS.md feat: add GitHub webhook forwarding to Feishu 2026-05-14 15:35:52 +08:00
CLAUDE.md feat: add GitHub webhook forwarding to Feishu 2026-05-14 15:35:52 +08:00
DESIGN.md initial commit 2026-03-09 22:24:10 +08:00
package-lock.json initial commit 2026-03-09 22:24:10 +08:00
package.json initial commit 2026-03-09 22:24:10 +08:00
README.md feat: add GitHub webhook forwarding to Feishu 2026-05-14 15:35:52 +08:00
wrangler.toml fix: handle GitHub form-encoded webhooks and add observability config 2026-05-14 07:59:18 +00:00

cloudflare-webhook-proxy

A Cloudflare Worker that receives webhook notifications (e.g. from Netdata, Grafana, Uptime Kuma, GitHub) 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
  • GitHub webhook support for Feishu/Lark cards, including X-GitHub-Event formatting and optional X-Hub-Signature-256 verification
  • Admin UI at /admin for 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


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.

npm test

All 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

  1. Open https://webhook-proxy.<your-subdomain>.workers.dev/admin in your browser

  2. Enter your ADMIN_AUTH_TOKEN and click Sign In

  3. Click + Add Route and fill in the form:

    Field Description
    Webhook Path URL segment that identifies this source, e.g. netdata
    Platform Telegram or Feishu
    Telegram Chat ID The chat/channel ID, e.g. -100123456789
    Bot Token Your Telegram bot token (Telegram only)
    Feishu Webhook URL The full webhook URL from Feishu (Feishu only)
    Source Auth Token Optional — callers send this as Bearer token; GitHub can use it as the webhook secret
  4. Click Add Route. The route is live immediately.

For GitHub webhooks, use a Feishu route and put the GitHub webhook secret in Source Auth Token if you want signature verification.

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>   (for generic webhooks when 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: GitHub webhook to Feishu/Lark

Create a Feishu route with a dedicated path such as github:

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": "github",
    "platform": "feishu",
    "channel_id": "https://open.feishu.cn/open-apis/bot/v2/hook/xxx",
    "auth_token": "github-webhook-secret"
  }'

Then configure the repository webhook in GitHub:

  • Payload URL: https://webhook-proxy.<your-subdomain>.workers.dev/webhook/github
  • Content type: application/json
  • Secret: the same value as auth_token
  • Events: choose the GitHub events you want forwarded

GitHub sends the event name in X-GitHub-Event and the delivery ID in X-GitHub-Delivery; when a secret is configured it signs the raw payload in X-Hub-Signature-256. The Feishu formatter uses the official GitHub webhook payload structure for events like push, pull_request, issues, issue_comment, release, workflow_run, check_run, and check_suite.

Official GitHub docs:

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 dev run, Wrangler creates a local KV store automatically using the preview_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": "optional Bearer token or GitHub webhook secret (null = public)",
  "feishu_secret": "optional Feishu custom bot signing secret"
}

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.