跳到內容

Webhook

透過 Webhook 讓你的伺服器即時收到群組事件通知。

群組事件發生 → 群組GM → HTTP POST → 你的伺服器

當訂閱的事件發生時,我們會發送 HTTP POST 請求到你指定的 URL。

你的伺服器需要提供一個 HTTPS endpoint:

// Express.js 範例
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const event = req.body;
console.log('收到事件:', event.event);
// 處理事件...
res.status(200).send('OK');
});
app.listen(3000);
  1. 前往「設定 > Webhook」
  2. 輸入你的 Webhook URL
  3. 選擇要訂閱的事件
  4. 點擊「儲存」

點擊「發送測試」確認你的伺服器能正確接收。

新訊息送出時觸發。

{
"event": "message.received",
"timestamp": "2025-01-15T08:30:00Z",
"data": {
"channelId": "C1234567890",
"messageId": "msg_001",
"sender": {
"userId": "U1234567890",
"displayName": "王小明"
},
"type": "text",
"content": "大家早安!"
}
}

新成員加入群組。

{
"event": "member.joined",
"timestamp": "2025-01-15T08:30:00Z",
"data": {
"channelId": "C1234567890",
"member": {
"userId": "U0987654321",
"displayName": "李小華"
}
}
}

成員離開群組。

{
"event": "member.left",
"timestamp": "2025-01-15T08:30:00Z",
"data": {
"channelId": "C1234567890",
"member": {
"userId": "U0987654321",
"displayName": "李小華"
}
}
}

檔案上傳。

{
"event": "file.uploaded",
"timestamp": "2025-01-15T08:30:00Z",
"data": {
"channelId": "C1234567890",
"messageId": "msg_002",
"sender": {
"userId": "U1234567890",
"displayName": "王小明"
},
"fileType": "image",
"fileSize": 102400
}
}

每個請求都包含簽章以確保來源:

X-Webhook-Signature: sha256=xxxxxx
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// 處理事件...
res.status(200).send('OK');
});

如果你的伺服器回傳非 2xx 狀態碼,我們會自動重試:

重試次數間隔時間
第 1 次1 分鐘後
第 2 次5 分鐘後
第 3 次30 分鐘後

超過 3 次重試失敗後,該事件會被標記為失敗。

Webhook 請求有 30 秒逾時。建議先回應再處理:

app.post('/webhook', async (req, res) => {
// 立即回應
res.status(200).send('OK');
// 非同步處理
processEventAsync(req.body).catch(console.error);
});

同一事件可能因重試而多次送達:

const processedEvents = new Set();
function handleEvent(event) {
const eventId = `${event.event}-${event.data.messageId || event.timestamp}`;
if (processedEvents.has(eventId)) {
return; // 已處理過
}
processedEvents.add(eventId);
// 處理事件...
}
app.post('/webhook', async (req, res) => {
try {
await processEvent(req.body);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
// 回傳 500 會觸發重試
res.status(500).send('Error');
}
});

在 Dashboard 的「設定 > Webhook > 推送記錄」查看:

  • 發送時間
  • 事件類型
  • 回應狀態
  • 回應時間
  • 錯誤訊息
  1. 確認 URL 使用 HTTPS
  2. 確認伺服器可從公網存取
  3. 檢查防火牆設定
  4. 使用「發送測試」排查

這是正常的重試機制,請實作冪等處理。

  1. 使用原始 request body
  2. 確認 secret 正確
  3. 使用 SHA256 演算法