PRO

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.

Send a Slack message when a meeting timer ends
Automatically close a sale when a flash sale countdown hits zero
Trigger the next slide in a presentation when a segment finishes
Log timer completions to your analytics platform
Start the next round in a game or quiz automatically

How Webhooks Work

1
Register webhook URL
2
Timer event occurs
3
Castimer sends POST
4
Server returns 200 OK
5
Delivery successful

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

EventTrigger
timer.startedTimer starts running
timer.pausedTimer is paused
timer.resumedTimer resumes from paused state
timer.completedTimer reaches zero
timer.resetTimer is reset to initial duration
interval.round_endOne interval round completes
interval.completedAll 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..."
}
FieldTypeDescription
eventstringEvent type identifier
timestampstringISO8601 time the event occurred
timer_idstringUnique identifier of the timer
titlestringTimer display title
typestringTimer type: countdown, event, interval
dataobjectEvent-specific data
signaturestringHMAC-SHA256 for request verification

Verifying Requests

Always verify the webhook signature to ensure the request came from Castimer and was not tampered with.

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.

Attempt 1 (initial)Immediate
Attempt 21 minute
Attempt 35 minutes
Attempt 430 minutes
Attempt 52 hours
After 5 failed attempts, delivery is marked as failed
Your endpoint must return a 200 response within 10 seconds. To avoid timeouts, acknowledge the webhook immediately and process the event asynchronously.

Testing Webhooks

Using webhook.site

  1. 1Go to webhook.site — you'll get a unique test URL
  2. 2Create a timer via API using that URL as webhook_url
  3. 3Trigger the timer event (start, complete, etc.)
  4. 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/castimer

Integration 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 });
});