Skip to main content
Roadmap Feature: Webhooks are currently in development. This documentation describes the planned implementation.

Overview

Webhooks allow you to receive real-time HTTP notifications when events occur in Day Copilot, eliminating the need for polling. Benefits:
  • Real-time updates
  • Reduced API calls
  • Lower latency
  • Event-driven architecture

Supported Events (Planned)

Task Events

  • task.created - New task created
  • task.updated - Task modified
  • task.deleted - Task removed
  • task.completed - Task marked as complete
  • task.assigned - Task assigned to user
  • task.comment_added - Comment added to task

Event Events

  • event.created - New calendar event created
  • event.updated - Event modified
  • event.deleted - Event removed
  • event.attendee_added - Attendee added
  • event.rsvp_changed - RSVP status updated

Context Events

  • context.created - New context created
  • context.shared - Context shared with user
  • context.updated - Context modified

Webhook Setup

1. Create Webhook Endpoint

Your endpoint must:
  • Accept POST requests
  • Return 200 status quickly (under 5 seconds)
  • Verify webhook signature (security)
  • Process events asynchronously
// Example Express.js endpoint
app.post('/webhooks/daycopilot', async (req, res) => {
  // Verify signature
  const signature = req.headers['x-daycopilot-signature'];
  if (!verifySignature(req.body, signature)) {
    return res.status(401).send('Invalid signature');
  }

  // Acknowledge receipt immediately
  res.status(200).send('OK');

  // Process event asynchronously
  processWebhookEvent(req.body).catch(console.error);
});

async function processWebhookEvent(event) {
  console.log('Received event:', event.type);

  switch (event.type) {
    case 'task.created':
      await handleTaskCreated(event.data);
      break;
    case 'task.updated':
      await handleTaskUpdated(event.data);
      break;
    // Handle other events...
  }
}

2. Register Webhook

POST /api/v1/webhooks
{
  "url": "https://your-app.com/webhooks/daycopilot",
  "events": [
    "task.created",
    "task.updated",
    "task.completed"
  ],
  "secret": "your-webhook-secret"
}
Response:
{
  "data": {
    "id": "webhook-uuid",
    "url": "https://your-app.com/webhooks/daycopilot",
    "events": ["task.created", "task.updated", "task.completed"],
    "created_at": "2025-11-02T12:00:00Z",
    "status": "active"
  }
}

3. Verify Webhook

Day Copilot will send a verification request:
{
  "type": "webhook.verification",
  "challenge": "random-string-123"
}
Respond with:
{
  "challenge": "random-string-123"
}

Webhook Payload Format

All webhook events follow this structure:
{
  "id": "event-uuid",
  "type": "task.created",
  "created_at": "2025-11-02T12:00:00Z",
  "data": {
    "task_id": "550e8400-e29b-41d4-a716-446655440000",
    "title": "New task from webhook",
    "status": "pending",
    "priority": "high",
    "user_id": "user-uuid"
  },
  "actor": {
    "user_id": "user-uuid",
    "email": "user@example.com"
  }
}
Fields:
  • id: Unique event identifier (for deduplication)
  • type: Event type
  • created_at: When event occurred
  • data: Event-specific data
  • actor: User who triggered the event

Event Examples

task.created

{
  "id": "event-123",
  "type": "task.created",
  "created_at": "2025-11-02T12:00:00Z",
  "data": {
    "task_id": "task-uuid",
    "title": "Complete documentation",
    "description": "Write API docs",
    "status": "pending",
    "priority": "high",
    "context_id": "project-uuid"
  },
  "actor": {
    "user_id": "user-uuid",
    "email": "alice@example.com"
  }
}

task.updated

{
  "id": "event-124",
  "type": "task.updated",
  "created_at": "2025-11-02T12:05:00Z",
  "data": {
    "task_id": "task-uuid",
    "changes": {
      "priority": {
        "old": "medium",
        "new": "urgent"
      },
      "status": {
        "old": "pending",
        "new": "in_progress"
      }
    }
  },
  "actor": {
    "user_id": "user-uuid",
    "email": "alice@example.com"
  }
}

task.assigned

{
  "id": "event-125",
  "type": "task.assigned",
  "created_at": "2025-11-02T12:10:00Z",
  "data": {
    "task_id": "task-uuid",
    "assignee": {
      "user_id": "assignee-uuid",
      "email": "bob@example.com"
    },
    "assigned_by": {
      "user_id": "user-uuid",
      "email": "alice@example.com"
    }
  }
}

Security

Signature Verification

Day Copilot signs all webhook requests:
X-DayCopilot-Signature: sha256=abc123...
Verify signature:
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = 'sha256=' + hmac.update(JSON.stringify(payload)).digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

// Usage
const isValid = verifySignature(
  req.body,
  req.headers['x-daycopilot-signature'],
  process.env.WEBHOOK_SECRET
);

if (!isValid) {
  return res.status(401).send('Invalid signature');
}

Best Practices

Never process webhooks without verifying signatures. This prevents spoofing and ensures authenticity.
Acknowledge webhook receipt within 5 seconds. Process events asynchronously to avoid timeouts.
app.post('/webhook', async (req, res) => {
  res.status(200).send('OK');  // Respond immediately
  await processLater(req.body); // Process async
});
Use event IDs for deduplication:
const processedEvents = new Set();

async function handleEvent(event) {
  if (processedEvents.has(event.id)) {
    console.log('Duplicate event, skipping');
    return;
  }
  processedEvents.add(event.id);
  await processEvent(event);
}
If your endpoint fails, Day Copilot will retry with exponential backoff:
  • Retry 1: After 1 minute
  • Retry 2: After 5 minutes
  • Retry 3: After 15 minutes
  • Retry 4: After 1 hour
  • Retry 5: After 6 hours (final attempt)

Managing Webhooks

List Webhooks

GET /api/v1/webhooks

Update Webhook

PUT /api/v1/webhooks/{webhookId}
{
  "events": ["task.created", "task.deleted"],
  "url": "https://new-url.com/webhook"
}

Delete Webhook

DELETE /api/v1/webhooks/{webhookId}

Test Webhook

Manually trigger a test event:
POST /api/v1/webhooks/{webhookId}/test
{
  "event_type": "task.created"
}

Webhook Logs

View delivery logs and retry history:
GET /api/v1/webhooks/{webhookId}/logs?limit=50
Response:
{
  "data": [
    {
      "id": "log-uuid",
      "event_id": "event-123",
      "event_type": "task.created",
      "delivered_at": "2025-11-02T12:00:00Z",
      "status_code": 200,
      "response_time_ms": 145,
      "attempts": 1
    },
    {
      "id": "log-uuid-2",
      "event_id": "event-124",
      "event_type": "task.updated",
      "delivered_at": "2025-11-02T12:05:00Z",
      "status_code": 500,
      "response_time_ms": 5000,
      "attempts": 2,
      "last_error": "Connection timeout"
    }
  ]
}

Common Patterns

Sync to External System

async function syncTaskToExternal(event) {
  if (event.type === 'task.created' || event.type === 'task.updated') {
    await externalSystem.upsertTask({
      id: event.data.task_id,
      title: event.data.title,
      status: event.data.status
    });
  } else if (event.type === 'task.deleted') {
    await externalSystem.deleteTask(event.data.task_id);
  }
}

Notification System

async function sendNotification(event) {
  if (event.type === 'task.assigned') {
    const assignee = event.data.assignee;
    const task = event.data;

    await sendEmail(assignee.email, {
      subject: `Task assigned: ${task.title}`,
      body: `You've been assigned to: ${task.title}`
    });
  }
}

Analytics Tracking

async function trackAnalytics(event) {
  await analytics.track(event.actor.user_id, event.type, {
    task_id: event.data.task_id,
    context_id: event.data.context_id,
    timestamp: event.created_at
  });
}

Testing Locally

Use ngrok for Local Development

# Start your local server
npm run dev  # Running on localhost:3000

# Expose via ngrok
ngrok http 3000
# Forwarding: https://abc123.ngrok.io -> localhost:3000

# Register webhook with ngrok URL
curl -X POST "https://app.daycopilot.ai/api/v1/webhooks" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "url": "https://abc123.ngrok.io/webhooks/daycopilot",
    "events": ["task.created"]
  }'

Mock Webhook Events

// For testing, simulate webhook events
function mockWebhookEvent(type, data) {
  return {
    id: `mock-${Date.now()}`,
    type,
    created_at: new Date().toISOString(),
    data,
    actor: {
      user_id: 'test-user',
      email: 'test@example.com'
    }
  };
}

// Test your handler
const mockEvent = mockWebhookEvent('task.created', {
  task_id: 'test-task',
  title: 'Test Task'
});

await handleEvent(mockEvent);

Current Status

Webhooks are planned for release in Q1 2026. Until then, use polling:
// Polling alternative (use until webhooks available)
async function pollForChanges() {
  let lastCheck = new Date();

  setInterval(async () => {
    const tasks = await fetch(
      `https://app.daycopilot.ai/api/v1/tasks?updated_after=${lastCheck.toISOString()}`,
      { headers: { 'Authorization': `Bearer ${token}` } }
    ).then(r => r.json());

    for (const task of tasks.data) {
      await handleTaskUpdate(task);
    }

    lastCheck = new Date();
  }, 60000); // Poll every minute
}

Next Steps

Feedback

We’re designing webhooks based on developer feedback. Let us know what events you need:

Request Webhook Features

Share your webhook requirements with our API team