Webhooks
Get notified when a timer starts, pauses, or hits zero. Connect to Slack, Zapier, or your own backend.
Webhooks
Webhooks let Castimer notify your server in real time when something happens to a timer — like when it starts, pauses, or hits zero. Instead of polling the API, your server receives an HTTP POST request the moment the event occurs.
How Webhooks Work
Setting Up a Webhook
Register a webhook URL when creating or updating a timer.
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"
}'Event Types
| Event | Trigger |
|---|---|
| timer.started | Timer starts running |
| timer.paused | Timer is paused |
| timer.resumed | Timer resumes from paused state |
| timer.completed | Timer reaches zero |
| timer.reset | Timer is reset to initial duration |
| interval.round_end | One interval round completes |
| interval.completed | All rounds in interval timer finish |
Payload Format
Every webhook delivers a JSON payload via HTTP POST. The Content-Type is 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..."
}| Field | Type | Description |
|---|---|---|
| event | string | Event type identifier |
| timestamp | string | ISO8601 time the event occurred |
| timer_id | string | Unique identifier of the timer |
| title | string | Timer display title |
| type | string | Timer type: countdown, event, interval |
| data | object | Event-specific data |
| signature | string | HMAC-SHA256 for request verification |
Verifying Requests
Castimer signs each webhook payload using HMAC-SHA256. The signature is included in the X-Castimer-Signature header and the payload's signature field.
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 });
});Retry Behavior
If your endpoint returns a non-2xx response or times out, Castimer will retry delivery with exponential backoff.
Testing Webhooks
Using webhook.site
- 1Go to webhook.site — you'll get a unique test URL
- 2Create a timer via API using that URL as
webhook_url - 3Trigger the timer event (start, complete, etc.)
- 4Watch the payload appear at webhook.site in real time
Using the Test Button (Dashboard)
From your dashboard, you can send a test payload to your webhook URL with a single click. Go to Dashboard → Timers → [timer] → Webhooks → Send Test.
Using local development (ngrok)
# Install ngrok and expose your local server
ngrok http 3000
# Use the ngrok URL as your webhook_url
# e.g. https://abc123.ngrok.io/webhooks/castimerIntegration Examples
Send Slack Notification
Post timer events to a Slack channel using Incoming Webhooks.
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 });
});Trigger Zapier Webhook
Forward timer events to Zapier to automate workflows.
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 });
});Auto-close a Flash Sale
Automatically close a sale when its countdown timer completes.
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 });
});