Skip to content

Commit b68ecaf

Browse files
committed
chore: 更新環境變數名稱為 ZSEND_WEBHOOKS_SECRET,並修改相關文檔
1 parent 1d754ae commit b68ecaf

3 files changed

Lines changed: 153 additions & 15 deletions

File tree

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
SECRET_KEY=your_secret_key_here
1+
ZSEND_WEBHOOKS_SECRET=your_secret_key_here

README.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# ZSend Webhooks
2+
3+
**Language:** English | [繁體中文](README.zh-TW.md)
4+
5+
ZSend Webhooks is a webhook receiver deployed on Cloudflare Workers. It accepts `POST` requests from ZSend, verifies `x-zsend-signature` and `x-zsend-timestamp`, and returns success only after confirming that the payload has not been tampered with.
6+
7+
## Features
8+
9+
- Accepts only `POST` webhook requests
10+
- Verifies HMAC SHA-256 signatures with `ZSEND_WEBHOOKS_SECRET`
11+
- Supports the ZSend test event: `X-ZSend-Event: test`
12+
- Runs on Cloudflare Workers
13+
- Tests Worker behavior with Vitest and `@cloudflare/vitest-pool-workers`
14+
15+
## Zeabur Test Event Exception
16+
17+
The current implementation has a temporary exception for `X-ZSend-Event: test`: whenever this event is received, the Worker returns `200 OK` directly without running signature verification.
18+
19+
This exists to work around a current Zeabur behavior. Zeabur sends the `test` event immediately after a webhook secret is provided, potentially before the endpoint has had a chance to finish configuring the matching `ZSEND_WEBHOOKS_SECRET`. At that moment, the receiver may not yet have the same secret as Zeabur, so the test event cannot pass the normal `x-zsend-signature` verification flow. The exception lets Zeabur correctly detect that the endpoint is alive.
20+
21+
After Zeabur fixes the test webhook signature behavior, this exception is expected to be removed so every webhook request goes through the same signature verification flow.
22+
23+
## Requirements
24+
25+
- Node.js 22+
26+
- npm
27+
- A Cloudflare account with Wrangler logged in
28+
29+
## Installation
30+
31+
```sh
32+
npm ci
33+
```
34+
35+
Create a local environment file:
36+
37+
```sh
38+
# macOS / Linux
39+
cp .env.example .env
40+
41+
# Windows PowerShell
42+
Copy-Item .env.example .env
43+
```
44+
45+
Then set the secret used for webhook signature verification:
46+
47+
```env
48+
ZSEND_WEBHOOKS_SECRET=your_secret_key_here
49+
```
50+
51+
## Local Development
52+
53+
Start the Cloudflare Workers development server:
54+
55+
```sh
56+
npm run dev
57+
```
58+
59+
To use the project `start` script:
60+
61+
```sh
62+
npm start
63+
```
64+
65+
## Deployment
66+
67+
First, store `ZSEND_WEBHOOKS_SECRET` as a Cloudflare Workers secret:
68+
69+
```sh
70+
npx wrangler secret put ZSEND_WEBHOOKS_SECRET
71+
```
72+
73+
Deploy the Worker:
74+
75+
```sh
76+
npm run deploy
77+
```
78+
79+
## Webhook Request Format
80+
81+
The Worker checks the following headers:
82+
83+
| Header | Description |
84+
| ------------------- | ---------------------------------------------------------- |
85+
| `x-zsend-event` | ZSend event name; `test` currently returns `OK!` directly |
86+
| `x-zsend-signature` | HMAC signature in the format `sha256=<hex_digest>` |
87+
| `x-zsend-timestamp` | Timestamp used to build the signature verification message |
88+
89+
The request body must be valid JSON.
90+
91+
Signature verification uses this message format:
92+
93+
```ts
94+
const message = `${timestamp}.${rawBody}`;
95+
const digest = await hmacSha256Hex(secret, message);
96+
const signature = `sha256=${digest}`;
97+
```
98+
99+
Note: `rawBody` must be the exact raw string sent as the request body. Reformatting JSON will produce a different signature.
100+
101+
## Responses
102+
103+
| Status | Body | Scenario |
104+
| ------ | -------------------------------------------------------------- | ---------------------------------------------------- |
105+
| `200` | `OK!` | Valid signature, or `X-ZSend-Event: test` is present |
106+
| `400` | `Missing signature or timestamp` | Missing signature or timestamp |
107+
| `400` | `Invalid JSON` | Body is not valid JSON |
108+
| `401` | `Invalid signature` | Signature verification failed |
109+
| `405` | `Method Not Allowed` | Request method is not `POST` |
110+
| `500` | `Server configuration error: ZSEND_WEBHOOKS_SECRET is not set` | Worker is missing `ZSEND_WEBHOOKS_SECRET` |
111+
112+
## Tests and Checks
113+
114+
```sh
115+
npm run test:run
116+
npm run typecheck
117+
npm run format:check
118+
npm run types:check
119+
```
120+
121+
During development, you can also use watch mode:
122+
123+
```sh
124+
npm test
125+
```
126+
127+
## Project Structure
128+
129+
```txt
130+
src/_index.ts Worker fetch entry point
131+
src/router.ts Webhook request validation and response logic
132+
src/unit/hmac.ts HMAC SHA-256 helper
133+
test/index.spec.ts Worker unit and integration tests
134+
wrangler.jsonc Cloudflare Workers configuration
135+
```

README.zh-TW.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# ZSend Webhooks
22

3-
ZSend Webhooks 是一個部署在 Cloudflare Workers 上的 webhook 接收端。它會接收 ZSend 傳來的 POST 請求,驗證 `x-zsend-signature``x-zsend-timestamp`,確認 payload 沒有被竄改後才回應成功。
3+
**語言:** [English](README.md) | 繁體中文
4+
5+
ZSend Webhooks 是一個部署在 Cloudflare Workers 上的 webhook 接收端。它會接收 ZSend 傳來的 `POST` 請求,驗證 `x-zsend-signature``x-zsend-timestamp`,確認 payload 沒有被竄改後才回應成功。
46

57
## 功能
68

79
- 只接受 `POST` webhook 請求
8-
- 使用 `SECRET_KEY` 驗證 HMAC SHA-256 簽章
10+
- 使用 `ZSEND_WEBHOOKS_SECRET` 驗證 HMAC SHA-256 簽章
911
- 支援 ZSend 測試事件:`X-ZSend-Event: test`
1012
- 使用 Cloudflare Workers 執行
1113
- 使用 Vitest 與 `@cloudflare/vitest-pool-workers` 測試 Worker 行為
@@ -14,7 +16,7 @@ ZSend Webhooks 是一個部署在 Cloudflare Workers 上的 webhook 接收端。
1416

1517
目前程式中對 `X-ZSend-Event: test` 有一個暫時性的例外處理:只要收到這個事件,就會直接回應 `200 OK`,不會進入簽章驗證流程。
1618

17-
這是為了相容 Zeabur 目前的設計缺陷。Zeabur 會在提供 webhook secret 的瞬間,甚至在 endpoint 端有機會完成 `SECRET_KEY` 設定之前,就先送出 `test` 事件。此時接收端還來不及填入與 Zeabur 相同的 secret,因此測試事件無法通過一般的 `x-zsend-signature` 驗證流程。為了讓 Zeabur 的測試功能可以正常判定 endpoint 存活,才暫時讓 `test` 事件無論簽章狀態如何都回應成功。
19+
這是為了相容 Zeabur 目前的行為。Zeabur 會在提供 webhook secret 的瞬間,甚至在 endpoint 端有機會完成 `ZSEND_WEBHOOKS_SECRET` 設定之前,就先送出 `test` 事件。此時接收端還來不及填入與 Zeabur 相同的 secret,因此測試事件無法通過一般的 `x-zsend-signature` 驗證流程。為了讓 Zeabur 的測試功能可以正常判定 endpoint 存活,才暫時讓 `test` 事件無論簽章狀態如何都回應成功。
1820

1921
等 Zeabur 官方修正測試 webhook 的簽章行為後,預計會移除這段 `test` 事件的例外處理,讓所有 webhook 請求都走一致的簽章驗證流程。
2022

@@ -43,7 +45,7 @@ Copy-Item .env.example .env
4345
接著設定 webhook 驗章用的 secret:
4446

4547
```env
46-
SECRET_KEY=your_secret_key_here
48+
ZSEND_WEBHOOKS_SECRET=your_secret_key_here
4749
```
4850

4951
## 本機開發
@@ -62,10 +64,10 @@ npm start
6264

6365
## 部署
6466

65-
先把 `SECRET_KEY` 設成 Cloudflare Workers secret:
67+
先把 `ZSEND_WEBHOOKS_SECRET` 設成 Cloudflare Workers secret:
6668

6769
```sh
68-
npx wrangler secret put SECRET_KEY
70+
npx wrangler secret put ZSEND_WEBHOOKS_SECRET
6971
```
7072

7173
部署 Worker:
@@ -98,14 +100,14 @@ const signature = `sha256=${digest}`;
98100

99101
## 回應狀態
100102

101-
| 狀態碼 | 回應 | 情境 |
102-
| ------ | --------------------------------------------------- | -------------------------------------- |
103-
| `200` | `OK!` | 簽章正確,或收到 `X-ZSend-Event: test` |
104-
| `400` | `Missing signature or timestamp` | 缺少簽章或 timestamp |
105-
| `400` | `Invalid JSON` | body 不是合法 JSON |
106-
| `401` | `Invalid signature` | 簽章驗證失敗 |
107-
| `405` | `Method Not Allowed` | 不是 `POST` 請求 |
108-
| `500` | `Server configuration error: SECRET_KEY is not set` | Worker 未設定 `SECRET_KEY` |
103+
| 狀態碼 | 回應 | 情境 |
104+
| ------ | -------------------------------------------------------------- | -------------------------------------- |
105+
| `200` | `OK!` | 簽章正確,或收到 `X-ZSend-Event: test` |
106+
| `400` | `Missing signature or timestamp` | 缺少簽章或 timestamp |
107+
| `400` | `Invalid JSON` | body 不是合法 JSON |
108+
| `401` | `Invalid signature` | 簽章驗證失敗 |
109+
| `405` | `Method Not Allowed` | 不是 `POST` 請求 |
110+
| `500` | `Server configuration error: ZSEND_WEBHOOKS_SECRET is not set` | Worker 未設定 `ZSEND_WEBHOOKS_SECRET` |
109111

110112
## 測試與檢查
111113

@@ -127,6 +129,7 @@ npm test
127129
```txt
128130
src/_index.ts Worker fetch 入口
129131
src/router.ts Webhook request 驗證與回應邏輯
132+
src/unit/hmac.ts HMAC SHA-256 輔助函式
130133
test/index.spec.ts Worker 單元與整合測試
131134
wrangler.jsonc Cloudflare Workers 設定
132135
```

0 commit comments

Comments
 (0)