Skip to main content

Events & Webhooks

Crew emits events for all significant actions. Subscribe to these events via webhooks to build real-time integrations.

Event Categories

Call Events

Events related to phone calls:
EventTrigger
call.initiatedCall started (outbound) or received (inbound)
call.ringingPhone is ringing
call.answeredCall connected
call.transferredCall transferred to another party
call.endedCall terminated
call.recording.readyRecording is available
call.transcription.readyTranscript is available

Agent Events

Events related to agent behavior:
EventTrigger
agent.intent.detectedAgent identified caller intent
agent.action.triggeredAgent executed an action
agent.escalationAgent escalated to human
agent.errorAgent encountered an error

SMS Events

Events related to text messages:
EventTrigger
sms.receivedInbound SMS received
sms.sentOutbound SMS sent
sms.deliveredSMS confirmed delivered
sms.failedSMS delivery failed

Appointment Events

Events related to scheduling:
EventTrigger
appointment.createdNew appointment booked
appointment.updatedAppointment modified
appointment.cancelledAppointment cancelled
appointment.confirmedAppointment confirmed
appointment.reminder.sentReminder sent

Campaign Events

Events related to outbound campaigns:
EventTrigger
campaign.createdCampaign created
campaign.startedCampaign began
campaign.pausedCampaign paused
campaign.completedCampaign finished
campaign.call.completedIndividual call in campaign completed

System Events

Platform-level events:
EventTrigger
webhook.testTest event
workspace.updatedWorkspace settings changed
agent.updatedAgent configuration changed

Event Payload Structure

All events follow this structure:
{
  "id": "evt_abc123xyz",
  "type": "call.ended",
  "created_at": "2024-01-15T10:30:00.000Z",
  "workspace_id": "ws_xyz789",
  "crew_id": "crew_abc123",
  "data": {
    // Event-specific data
  }
}

Event Details

call.initiated

{
  "id": "evt_abc123",
  "type": "call.initiated",
  "created_at": "2024-01-15T10:30:00Z",
  "data": {
    "call_id": "call_def456",
    "direction": "outbound",
    "from": "+14155559999",
    "to": "+14155551234",
    "agent_id": "agent_sarah",
    "campaign_id": "camp_xyz789"
  }
}

call.ended

{
  "id": "evt_abc123",
  "type": "call.ended",
  "created_at": "2024-01-15T10:35:00Z",
  "data": {
    "call_id": "call_def456",
    "direction": "outbound",
    "from": "+14155559999",
    "to": "+14155551234",
    "agent_id": "agent_sarah",
    "duration_seconds": 287,
    "disposition": "completed",
    "outcome": {
      "intent": "schedule_appointment",
      "appointment_booked": true,
      "appointment_id": "apt_123"
    },
    "transcript_available": true,
    "recording_available": true
  }
}

agent.intent.detected

{
  "id": "evt_abc123",
  "type": "agent.intent.detected",
  "created_at": "2024-01-15T10:32:00Z",
  "data": {
    "call_id": "call_def456",
    "agent_id": "agent_sarah",
    "intent": "schedule_appointment",
    "confidence": 0.94,
    "entities": {
      "date": "2024-01-20",
      "time": "14:00",
      "service": "consultation"
    }
  }
}

appointment.created

{
  "id": "evt_abc123",
  "type": "appointment.created",
  "created_at": "2024-01-15T10:33:00Z",
  "data": {
    "appointment_id": "apt_123",
    "call_id": "call_def456",
    "patient": {
      "name": "John Smith",
      "phone": "+14155551234"
    },
    "provider": "Dr. Johnson",
    "datetime": "2024-01-20T14:00:00Z",
    "duration_minutes": 30,
    "type": "consultation",
    "status": "confirmed"
  }
}

campaign.call.completed

{
  "id": "evt_abc123",
  "type": "campaign.call.completed",
  "created_at": "2024-01-15T10:35:00Z",
  "data": {
    "campaign_id": "camp_xyz789",
    "call_id": "call_def456",
    "contact": {
      "phone": "+14155551234",
      "name": "John Smith"
    },
    "outcome": "confirmed",
    "attempts": 1,
    "campaign_progress": {
      "completed": 156,
      "remaining": 844,
      "percent_complete": 15.6
    }
  }
}

Subscribing to Events

Create Webhook

curl -X POST https://api.usecrew.ai/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "url": "https://yourapp.com/webhooks/crew",
    "events": [
      "call.ended",
      "appointment.created",
      "sms.received"
    ],
    "secret": "your_webhook_secret"
  }'

Update Events

curl -X PATCH https://api.usecrew.ai/v1/webhooks/wh_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "events": [
      "call.ended",
      "call.transferred"
    ]
  }'

Wildcard Subscriptions

Subscribe to all events of a type:
{
  "events": [
    "call.*",
    "appointment.*"
  ]
}
Subscribe to all events:
{
  "events": ["*"]
}

Handling Webhooks

Verification

Verify webhook signatures:
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signature)
  );
}

app.post('/webhooks/crew', (req, res) => {
  const signature = req.headers['x-crew-signature'];
  const isValid = verifySignature(
    JSON.stringify(req.body),
    signature,
    process.env.WEBHOOK_SECRET
  );
  
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process event
  processEvent(req.body);
  
  // Respond quickly
  res.status(200).json({ received: true });
});

Idempotency

Handle duplicate events using the event ID:
async function processEvent(event) {
  // Check if already processed
  const exists = await db.events.findOne({ id: event.id });
  if (exists) {
    console.log(`Event ${event.id} already processed`);
    return;
  }
  
  // Process the event
  await handleEvent(event);
  
  // Mark as processed
  await db.events.insert({ id: event.id, processed_at: new Date() });
}

Async Processing

Process webhooks asynchronously for reliability:
app.post('/webhooks/crew', async (req, res) => {
  // Validate signature
  if (!verifySignature(req)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Queue for processing
  await queue.add('process-webhook', req.body);
  
  // Respond immediately
  res.status(200).json({ received: true });
});

// Process in background
queue.process('process-webhook', async (job) => {
  await handleEvent(job.data);
});

Retry Policy

Failed webhooks are retried:
AttemptDelay
1Immediate
230 seconds
32 minutes
410 minutes
51 hour
After 5 failures, the event is marked as failed and not retried.

Retry Headers

X-Crew-Delivery-Attempt: 2
X-Crew-First-Attempt: 2024-01-15T10:30:00Z

Monitoring

Webhook Logs

View delivery history:
curl https://api.usecrew.ai/v1/webhooks/wh_abc123/logs \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "logs": [
    {
      "event_id": "evt_123",
      "event_type": "call.ended",
      "delivered_at": "2024-01-15T10:30:01Z",
      "response_status": 200,
      "response_time_ms": 145
    },
    {
      "event_id": "evt_124",
      "event_type": "call.ended",
      "delivered_at": "2024-01-15T10:31:00Z",
      "response_status": 500,
      "retries": 2,
      "last_retry_at": "2024-01-15T10:33:00Z"
    }
  ]
}

Failed Events

List events that exhausted retries:
curl https://api.usecrew.ai/v1/webhooks/wh_abc123/failed \
  -H "Authorization: Bearer YOUR_API_KEY"

Replay Events

Replay failed or historical events:
curl -X POST https://api.usecrew.ai/v1/webhooks/wh_abc123/replay \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "event_ids": ["evt_123", "evt_124"]
  }'

Testing

Send Test Event

curl -X POST https://api.usecrew.ai/v1/webhooks/wh_abc123/test \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "event_type": "call.ended"
  }'

Local Development

Use ngrok or similar tools:
ngrok http 3000
Then set your webhook URL to the ngrok address.

Best Practices

Return 200 within 5 seconds. Process async.
Use event IDs for idempotency.
Always validate webhook signatures.
Track webhook delivery success rates.
Build retry logic for downstream systems.

Next Steps