PRO
Webhooks
当计时器启动、暂停或归零时收到通知。连接到 Slack、Zapier 或您自己的后端。
Webhooks
Webhooks 让 Castimer 能够在计时器发生事件时实时通知您的服务器——比如启动、暂停或归零。无需轮询 API,您的服务器会在事件发生的瞬间收到 HTTP POST 请求。
会议计时器结束时发送 Slack 通知
限时促销倒计时归零时自动关闭促销
演示环节结束时自动切换到下一张幻灯片
将计时器完成记录发送到分析平台
游戏或问答中自动开始下一轮
工作原理
1
注册 Webhook URL2
计时器事件触发3
Castimer 发送 POST4
服务器返回 2005
投递成功设置 Webhook
在创建或更新计时器时注册 Webhook URL。
curl -X POST https://castimer.com/api/v1/timers \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Flash Sale",
"type": "event",
"target_date": "2026-06-01T00:00:00Z",
"webhook_url": "https://yourapp.com/webhooks/castimer"
}'事件类型
| 事件 | 触发条件 |
|---|---|
| timer.started | 计时器开始运行 |
| timer.paused | 计时器被暂停 |
| timer.resumed | 计时器从暂停状态恢复 |
| timer.completed | 计时器归零 |
| timer.reset | 计时器重置为初始时长 |
| interval.round_end | 间隔计时器的一轮完成 |
| interval.completed | 间隔计时器所有轮次完成 |
数据格式
每个 Webhook 都通过 HTTP POST 传递 JSON 数据。Content-Type 为 application/json。
{
"event": "timer.completed",
"timestamp": "2026-05-10T14:30:00Z",
"timer_id": "tmr_abc123xyz",
"title": "Flash Sale Countdown",
"type": "event",
"data": {
"state": "completed",
"duration": 1800,
"actual_duration": 1803,
"started_at": "2026-05-10T14:00:00Z",
"completed_at": "2026-05-10T14:30:00Z"
},
"signature": "sha256=a8f3b2c..."
}| 字段 | 类型 | 说明 |
|---|---|---|
| event | string | 事件类型标识符 |
| timestamp | string | 事件发生的 ISO8601 时间 |
| timer_id | string | 计时器的唯一标识符 |
| title | string | 计时器显示标题 |
| type | string | 计时器类型:countdown、event、interval |
| data | object | 事件特定数据 |
| signature | string | 用于请求验证的 HMAC-SHA256 签名 |
验证请求
请务必验证 Webhook 签名,以确保请求来自 Castimer 且未被篡改。
Castimer 使用 HMAC-SHA256 对每个 Webhook 数据进行签名。签名包含在 X-Castimer-Signature 请求头和数据体的 signature 字段中。
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return `sha256=${expected}` === signature;
}
// In your webhook handler:
app.post('/webhooks/castimer', (req, res) => {
const signature = req.headers['x-castimer-signature'];
const isValid = verifyWebhook(req.body, signature, process.env.CASTIMER_WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, timer_id, title } = req.body;
console.log(`Event: ${event} | Timer: ${title} (${timer_id})`);
res.status(200).json({ received: true });
});重试机制
如果您的端点返回非 2xx 响应或超时,Castimer 将使用指数退避策略重试投递。
第 1(首次) 次立即
第 2 次1 分钟
第 3 次5 分钟
第 4 次30 分钟
第 5 次2 小时
5 次失败后,投递标记为失败
您的端点必须在 10 秒内返回 200 响应。为避免超时,请立即确认 Webhook 并异步处理事件。
测试 Webhook
使用 webhook.site
- 1访问 webhook.site ,您将获得一个唯一的测试 URL
- 2通过 API 创建计时器,将该 URL 作为
webhook_url - 3触发计时器事件(启动、完成等)
- 4在 webhook.site 实时观察数据到达
使用测试按钮(Dashboard)
在您的控制面板中,可以一键发送测试数据到 Webhook URL。前往 控制面板 → 计时器 → [计时器] → Webhooks → 发送测试。
本地开发(ngrok)
# 安装 ngrok 并暴露本地服务
ngrok http 3000
# 使用 ngrok URL 作为 webhook_url
# 例如 https://abc123.ngrok.io/webhooks/castimer集成示例
发送 Slack 通知
使用 Incoming Webhooks 将计时器事件发送到 Slack 频道。
app.post('/webhooks/castimer', async (req, res) => {
// Verify signature first (see Verifying Requests)
const { event, title, timer_id } = req.body;
if (event === 'timer.completed') {
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `⏰ Timer completed: *${title}*`,
blocks: [{
type: 'section',
text: {
type: 'mrkdwn',
text: `⏰ *${title}* has finished!\nTimer ID: \`${timer_id}\``
}
}]
})
});
}
res.status(200).json({ received: true });
});触发 Zapier Webhook
将计时器事件转发到 Zapier 以自动化工作流。
app.post('/webhooks/castimer', async (req, res) => {
const { event, title, data } = req.body;
if (event === 'timer.completed') {
// Forward to Zapier catch hook
await fetch(process.env.ZAPIER_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
timer_title: title,
completed_at: data.completed_at,
event_type: event
})
});
}
res.status(200).json({ received: true });
});自动关闭限时促销
当倒计时计时器完成时自动关闭促销。
app.post('/webhooks/castimer', async (req, res) => {
const { event, timer_id } = req.body;
if (event === 'timer.completed') {
// Find the sale linked to this timer
const sale = await db.sales.findOne({ castimer_id: timer_id });
if (sale) {
// Close the sale automatically
await db.sales.update(
{ id: sale.id },
{ status: 'closed', closed_at: new Date() }
);
console.log(`Sale ${sale.id} closed — timer ${timer_id} completed`);
}
}
res.status(200).json({ received: true });
});