DevelopersWebhooks

Webhooks

Receive real-time notifications about conversation session events through webhooks.

Overview

LiveTok webhooks allow you to subscribe to session events and receive HTTP POST notifications when conversations begin or end. Build real-time integrations, automate workflows, and track conversation lifecycle.

Webhook Basics

How Webhooks Work

1. Session event occurs in LiveTok (conversation begins/ends)
2. LiveTok sends HTTP POST to your endpoint
3. Your server receives and processes the event
4. Your server returns 200 OK status
5. If no 200 received, LiveTok retries
```text

### Setting Up Webhooks

**Via Dashboard:**
1. Go to **Settings** > **Webhooks**
2. Click **Add Webhook**
3. Enter your endpoint URL
4. Select events to subscribe to: `session.begin` and `session.end`
5. Save webhook

**Via API:**
```bash
curl https://api.livetok.ai/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/livetok",
    "events": ["session.begin", "session.end"],
    "description": "Session tracking webhook"
  }'
```text

---

## Available Events

### Session Begin

Triggered when a new conversation session begins.

**session.begin**
```json
{
  "type": "session.begin",
  "id": "evt_abc123",
  "created_at": "2024-03-15T10:30:00Z",
  "livemode": true,
  "data": {
    "session_id": "sess_xyz789",
    "conversation_id": "conv_abc123",
    "channel": "whatsapp",
    "customer": {
      "id": "cust_def456",
      "name": "John Doe",
      "phone": "+1234567890",
      "email": "john@example.com"
    },
    "agent_type": "ai_agent",
    "started_at": "2024-03-15T10:30:00Z",
    "metadata": {
      "source": "organic",
      "campaign": null
    }
  }
}
```text

**Fields:**
- `session_id` - Unique identifier for this session
- `conversation_id` - ID of the conversation
- `channel` - Communication channel (whatsapp, telephony, web)
- `customer` - Customer information
- `agent_type` - Type of agent handling the session (ai_agent, human_agent)
- `started_at` - Timestamp when session began
- `metadata` - Additional custom data

### Session End

Triggered when a conversation session ends.

**session.end**
```json
{
  "type": "session.end",
  "id": "evt_ghi789",
  "created_at": "2024-03-15T10:45:00Z",
  "livemode": true,
  "data": {
    "session_id": "sess_xyz789",
    "conversation_id": "conv_abc123",
    "channel": "whatsapp",
    "customer": {
      "id": "cust_def456",
      "name": "John Doe",
      "phone": "+1234567890",
      "email": "john@example.com"
    },
    "agent_type": "ai_agent",
    "started_at": "2024-03-15T10:30:00Z",
    "ended_at": "2024-03-15T10:45:00Z",
    "duration_seconds": 900,
    "message_count": 12,
    "resolution_status": "resolved",
    "satisfaction_score": 5,
    "metadata": {
      "source": "organic",
      "campaign": null
    }
  }
}
```text

**Fields:**
- `session_id` - Unique identifier for this session
- `conversation_id` - ID of the conversation
- `channel` - Communication channel
- `customer` - Customer information
- `agent_type` - Type of agent that handled the session
- `started_at` - Timestamp when session began
- `ended_at` - Timestamp when session ended
- `duration_seconds` - Total session duration in seconds
- `message_count` - Number of messages exchanged
- `resolution_status` - Final status (resolved, pending, transferred)
- `satisfaction_score` - Customer satisfaction rating (1-5, if available)
- `metadata` - Additional custom data

---

## Webhook Payload Structure

### Standard Format

All webhooks follow this structure:

```json
{
  "id": "evt_unique_id",
  "type": "event.name",
  "created_at": "2024-03-15T10:30:00Z",
  "livemode": true,
  "data": {
    // Event-specific data
  }
}
```text

### Headers

LiveTok includes these headers:

```text
Content-Type: application/json
User-Agent: LiveTok-Webhook/1.0
X-LiveTok-Event: session.begin
X-LiveTok-Signature: sha256=abc123...
X-LiveTok-Delivery: unique_delivery_id
```text

---

## Security

### Verifying Webhooks

Always verify webhook signatures to ensure requests are from LiveTok:

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = 'sha256=' + hmac.update(payload).digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

// Express.js example
app.post('/webhooks/livetok', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-livetok-signature'];
  const payload = req.body;

  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // Process webhook
  const event = JSON.parse(payload);
  handleEvent(event);

  res.status(200).send('OK');
});
```text

**Python Example:**
```python
import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)
```text

### Webhook Secret

Get your webhook secret:

1. Go to **Settings** > **Webhooks**
2. Click on your webhook
3. View **Signing Secret**
4. Store securely in environment variables

---

## Handling Webhooks

### Basic Handler

```javascript
app.post('/webhooks/livetok', async (req, res) => {
  const event = req.body;

  // Respond quickly
  res.status(200).send('OK');

  // Process asynchronously
  processEvent(event).catch(err => {
    console.error('Error processing webhook:', err);
  });
});

async function processEvent(event) {
  switch (event.type) {
    case 'session.begin':
      await handleSessionBegin(event.data);
      break;

    case 'session.end':
      await handleSessionEnd(event.data);
      break;

    default:
      console.log('Unhandled event type:', event.type);
  }
}
```text

### Event Processing

**Best Practices:**
1. Respond with 200 immediately
2. Process events asynchronously
3. Implement idempotency
4. Handle duplicate events
5. Log all webhooks
6. Monitor failures

**Idempotent Processing:**
```javascript
const processedEvents = new Set();

async function processEvent(event) {
  // Check if already processed
  if (processedEvents.has(event.id)) {
    console.log('Event already processed:', event.id);
    return;
  }

  // Process event
  await handleEvent(event);

  // Mark as processed
  processedEvents.add(event.id);

  // In production, use database instead of in-memory Set
}
```text

---

## Retry Logic

### Automatic Retries

LiveTok automatically retries failed webhooks:

**Retry Schedule:**
```text
Immediately
After 5 minutes
After 15 minutes
After 1 hour
After 3 hours
After 6 hours
After 12 hours
After 24 hours
```text

**Retry Conditions:**
- HTTP status != 200
- Connection timeout (10 seconds)
- Connection error

**Retry Headers:**
```text
X-LiveTok-Delivery-Attempt: 3
X-LiveTok-First-Attempt: 2024-03-15T10:30:00Z
```text

### Handling Retries

```javascript
app.post('/webhooks/livetok', async (req, res) => {
  const event = req.body;
  const attempt = req.headers['x-livetok-delivery-attempt'];

  // Check for duplicate processing
  const alreadyProcessed = await checkIfProcessed(event.id);

  if (alreadyProcessed) {
    console.log(`Event ${event.id} already processed (attempt ${attempt})`);
    return res.status(200).send('OK');
  }

  // Process event
  try {
    await processEvent(event);
    await markAsProcessed(event.id);
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error processing webhook:', error);

    // Return 200 to stop retries if it's a permanent error
    if (error.isPermanent) {
      res.status(200).send('Permanent error');
    } else {
      // Return 500 to trigger retry
      res.status(500).send('Temporary error');
    }
  }
});
```text

---

## Use Cases

### Track Session Analytics

```javascript
async function handleSessionEnd(data) {
  // Send to analytics platform
  await analytics.track({
    event: 'Session Completed',
    userId: data.customer.id,
    properties: {
      session_id: data.session_id,
      conversation_id: data.conversation_id,
      channel: data.channel,
      duration_seconds: data.duration_seconds,
      message_count: data.message_count,
      resolution_status: data.resolution_status,
      satisfaction_score: data.satisfaction_score
    }
  });

  // Update metrics dashboard
  await metrics.increment('sessions_completed');
  await metrics.timing('session_duration', data.duration_seconds);
}
```text

### Trigger Workflows

```javascript
async function handleSessionBegin(data) {
  // Check if VIP customer
  if (data.customer.segment === 'vip') {
    // Notify VIP support team
    await slack.chat.postMessage({
      channel: '#vip-support',
      text: `New VIP session started`,
      blocks: [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*VIP Session Started*\nCustomer: ${data.customer.name}\nChannel: ${data.channel}`
          }
        }
      ]
    });
  }

  // Log session start
  await database.sessions.create({
    session_id: data.session_id,
    conversation_id: data.conversation_id,
    customer_id: data.customer.id,
    started_at: data.started_at,
    channel: data.channel
  });
}
```text

### Sync to External Systems

```javascript
async function handleSessionEnd(data) {
  // Sync to CRM
  await crm.activities.create({
    contact_id: data.customer.id,
    type: 'conversation',
    channel: data.channel,
    duration: data.duration_seconds,
    status: data.resolution_status,
    satisfaction_score: data.satisfaction_score,
    timestamp: data.ended_at
  });

  // Update customer profile
  await crm.contacts.update(data.customer.id, {
    last_contact_date: data.ended_at,
    total_sessions: data.customer.total_conversations + 1
  });
}
```text

---

## Testing Webhooks

### Local Testing

**Using ngrok:**
```bash
# Start your local server
node server.js

# In another terminal, expose it
ngrok http 3000

# Use the ngrok URL in your webhook settings
https://abc123.ngrok.io/webhooks/livetok
```text

### Test Events

Send test events from dashboard:

1. Go to **Settings** > **Webhooks**
2. Click on your webhook
3. Click **Send Test Event**
4. Select event type (session.begin or session.end)
5. View delivery status

**Via API:**
```bash
curl https://api.livetok.ai/v1/webhooks/whook_123/test \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"event_type": "session.begin"}'
```text

### Monitoring Deliveries

View webhook delivery history:

```bash
# Get recent deliveries
curl https://api.livetok.ai/v1/webhooks/whook_123/deliveries \
  -H "Authorization: Bearer YOUR_API_KEY"
```text

**Response:**
```json
{
  "deliveries": [
    {
      "id": "del_abc123",
      "event_id": "evt_xyz789",
      "event_type": "session.begin",
      "url": "https://yourapp.com/webhooks",
      "status": "succeeded",
      "response_code": 200,
      "attempts": 1,
      "created_at": "2024-03-15T10:30:00Z",
      "completed_at": "2024-03-15T10:30:01Z"
    }
  ]
}
```text

---

## Webhook Management

### List Webhooks

```bash
curl https://api.livetok.ai/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY"
```text

### Update Webhook

```bash
curl https://api.livetok.ai/v1/webhooks/whook_123 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -X PATCH \
  -d '{"events": ["session.begin", "session.end"]}'
```text

### Delete Webhook

```bash
curl https://api.livetok.ai/v1/webhooks/whook_123 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -X DELETE
```text

### Disable Webhook

```bash
curl https://api.livetok.ai/v1/webhooks/whook_123 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -X PATCH \
  -d '{"enabled": false}'
```text

---

## Troubleshooting

### Common Issues

**Webhook not receiving events:**
- Check URL is publicly accessible
- Verify webhook is enabled
- Ensure events are subscribed (session.begin, session.end)
- Check firewall/security settings

**Signature verification failing:**
- Using correct webhook secret
- Using raw request body (not parsed JSON)
- Secret not exposed in logs
- Signature header name is correct

**High failure rate:**
- Server responding slowly (>10s timeout)
- Server returning non-200 status
- Connection issues
- Processing errors

### Debug Mode

Enable debug logging:

```javascript
app.post('/webhooks/livetok', (req, res) => {
  console.log('Webhook received:', {
    event_id: req.body.id,
    event_type: req.body.type,
    signature: req.headers['x-livetok-signature'],
    attempt: req.headers['x-livetok-delivery-attempt']
  });

  // Your processing logic
});
```text

---

## Best Practices

### Performance

**Do:**
- Respond with 200 immediately
- Process asynchronously in background
- Use message queue for processing
- Implement timeouts
- Monitor processing times

**Don't:**
- Block response waiting for processing
- Make synchronous external calls
- Process in webhook handler
- Ignore timeouts

### Reliability

**Do:**
- Implement idempotency
- Handle duplicate events
- Log all webhooks
- Monitor failures
- Set up alerts
- Test error scenarios

**Don't:**
- Process same event multiple times
- Ignore delivery failures
- Skip error handling
- Forget to test

### Security

**Do:**
- Always verify signatures
- Use HTTPS endpoints
- Store secrets securely
- Rotate secrets regularly
- Limit webhook access
- Monitor for anomalies

**Don't:**
- Skip signature verification
- Use HTTP (non-secure)
- Hardcode secrets
- Expose secrets in logs
- Allow public access

---

## Next Steps

- [Explore API Documentation →](/developers/api)
- [View Integration Examples →](/integrations)
- [Visit Support Page →](/support)

---

## Need Help?

- Chat with our AI Agent (knows webhooks!)
- Email developers@livetok.ai
- Check our [Support page](/support)