Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sockudo/sockudo/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Webhooks allow Sockudo to send HTTP notifications when events occur, such as channel occupancy changes, client connections/disconnections, and custom events. With batching support, you can efficiently handle high-volume events. Key Features:
  • HTTP webhook endpoints
  • AWS Lambda integration
  • Event batching for efficiency
  • Configurable batch size and duration
  • Retry logic with exponential backoff

Quick Start

Basic Configuration

config/config.json
{
  "webhooks": {
    "batching": {
      "enabled": false,
      "duration": 50,
      "size": 50
    }
  },
  "apps": [
    {
      "id": "my-app",
      "key": "my-key",
      "secret": "my-secret",
      "webhooks": [
        {
          "url": "https://example.com/webhook",
          "event_types": ["channel_occupied", "channel_vacated"]
        }
      ]
    }
  ]
}

Environment Variables

WEBHOOK_BATCHING_ENABLED=true
WEBHOOK_BATCHING_DURATION=50
WEBHOOK_BATCHING_SIZE=50

Webhook Configuration

Per-App Webhooks

Configure webhooks for individual applications:
{
  "apps": [
    {
      "id": "my-app",
      "webhooks": [
        {
          "url": "https://example.com/webhook",
          "event_types": [
            "channel_occupied",
            "channel_vacated",
            "member_added",
            "member_removed"
          ],
          "headers": {
            "X-Custom-Header": "value",
            "Authorization": "Bearer token"
          }
        }
      ]
    }
  ]
}
OptionTypeDescription
urlstringWebhook endpoint URL (HTTP/HTTPS)
event_typesarrayList of events to trigger webhook
headersobjectOptional custom HTTP headers

Supported Event Types

Event TypeDescriptionPayload
channel_occupiedFirst client subscribes to channel{"channel": "...", "time_ms": 123}
channel_vacatedLast client unsubscribes from channel{"channel": "...", "time_ms": 123}
member_addedMember joins presence channel{"channel": "...", "user_id": "..."}
member_removedMember leaves presence channel{"channel": "...", "user_id": "..."}
client_eventCustom client event (if enabled){"channel": "...", "event": "...", "data": {...}}

Batching

Why Batching?

Without batching, each event triggers a separate HTTP request:
100 events/sec = 100 HTTP requests/sec
1000 events/sec = 1000 HTTP requests/sec
With batching, events are grouped:
1000 events/sec with batch_size=50 = 20 HTTP requests/sec
Saving: 98% reduction in HTTP requests

Batching Configuration

{
  "webhooks": {
    "batching": {
      "enabled": true,
      "duration": 50,    // milliseconds
      "size": 50         // max events per batch
    }
  }
}
OptionDefaultDescription
enabledfalseEnable/disable batching
duration50Max time (ms) to wait before sending batch
size50Max events per batch

Batching Behavior

Batch is sent when either condition is met:
  • Batch size reaches size (e.g., 50 events)
  • Duration timeout reaches duration (e.g., 50ms)
Example with duration=50ms, size=50:
High traffic (>50 events/50ms):
  → Batch sent when 50 events collected (before 50ms)
  → Result: Maximum efficiency

Low traffic (<50 events/50ms):
  → Batch sent after 50ms timeout
  → Result: Maximum 50ms delay

Batched Webhook Payload

{
  "time_ms": 1678901234567,
  "events": [
    {
      "name": "channel_occupied",
      "channel": "presence-room-1",
      "time_ms": 1678901234567
    },
    {
      "name": "member_added",
      "channel": "presence-room-1",
      "user_id": "user-123"
    },
    {
      "name": "channel_vacated",
      "channel": "presence-room-2",
      "time_ms": 1678901234600
    }
  ]
}

Non-Batched Webhook Payload

{
  "name": "channel_occupied",
  "channel": "presence-room-1",
  "time_ms": 1678901234567
}

AWS Lambda Integration

Webhooks can trigger AWS Lambda functions directly:

Configuration

{
  "apps": [
    {
      "id": "my-app",
      "webhooks": [
        {
          "url": "lambda://arn:aws:lambda:us-east-1:123456789:function:my-webhook",
          "event_types": ["channel_occupied", "channel_vacated"]
        }
      ]
    }
  ]
}

Lambda Function Example

import json

def lambda_handler(event, context):
    # Parse webhook payload
    body = json.loads(event['body'])
    
    if 'events' in body:  # Batched
        for webhook_event in body['events']:
            process_event(webhook_event)
    else:  # Single event
        process_event(body)
    
    return {
        'statusCode': 200,
        'body': json.dumps({'status': 'ok'})
    }

def process_event(webhook_event):
    event_type = webhook_event['name']
    channel = webhook_event['channel']
    
    if event_type == 'channel_occupied':
        print(f"Channel occupied: {channel}")
    elif event_type == 'channel_vacated':
        print(f"Channel vacated: {channel}")

Lambda Permissions

Ensure Sockudo has permission to invoke Lambda:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:us-east-1:123456789:function:my-webhook"
    }
  ]
}

Retry Logic

Webhooks include automatic retry with exponential backoff:

Retry Configuration

Max retries: 3
Backoff: exponential
Initial delay: 1 second
Max delay: 60 seconds

Retry Behavior

Attempt 1: Immediate Attempt 2: Wait 1 second, retry Attempt 3: Wait 2 seconds, retry Attempt 4: Wait 4 seconds, retry Failed: Log error, give up

Retryable Errors

Status CodeRetry?Reason
429 (Too Many Requests)✅ YesTemporary rate limit
500 (Internal Server Error)✅ YesTemporary server issue
502 (Bad Gateway)✅ YesTemporary proxy issue
503 (Service Unavailable)✅ YesTemporary unavailability
504 (Gateway Timeout)✅ YesTemporary timeout
400 (Bad Request)❌ NoInvalid payload
401 (Unauthorized)❌ NoAuthentication failed
404 (Not Found)❌ NoEndpoint doesn’t exist

Use Cases

1. Channel Activity Tracking

Problem: Need to track when channels become active/inactive Solution:
{
  "webhooks": [
    {
      "url": "https://analytics.example.com/channel-activity",
      "event_types": ["channel_occupied", "channel_vacated"]
    }
  ]
}
Webhook Handler:
app.post('/channel-activity', (req, res) => {
  const { name, channel, time_ms } = req.body;
  
  if (name === 'channel_occupied') {
    // Track channel becoming active
    analytics.track('channel_active', { channel, time_ms });
  } else if (name === 'channel_vacated') {
    // Track channel becoming inactive
    analytics.track('channel_inactive', { channel, time_ms });
  }
  
  res.status(200).send('OK');
});

2. Presence Tracking

Problem: Need to track users joining/leaving presence channels Solution:
{
  "webhooks": [
    {
      "url": "https://presence.example.com/updates",
      "event_types": ["member_added", "member_removed"]
    }
  ]
}
Webhook Handler:
app.post('/updates', (req, res) => {
  const { name, channel, user_id } = req.body;
  
  if (name === 'member_added') {
    // Update presence database
    db.query(
      'INSERT INTO presence (channel, user_id, joined_at) VALUES (?, ?, NOW())',
      [channel, user_id]
    );
  } else if (name === 'member_removed') {
    // Remove from presence database
    db.query(
      'DELETE FROM presence WHERE channel = ? AND user_id = ?',
      [channel, user_id]
    );
  }
  
  res.status(200).send('OK');
});

3. Real-Time Analytics

Problem: Need to aggregate real-time events for dashboards Solution:
{
  "webhooks": {
    "batching": {
      "enabled": true,
      "duration": 1000,  // 1 second
      "size": 100
    }
  }
}
Webhook Handler:
app.post('/analytics', (req, res) => {
  const { events } = req.body;
  
  // Batch process events
  const stats = {
    channel_occupied: 0,
    channel_vacated: 0,
    member_added: 0,
    member_removed: 0
  };
  
  events.forEach(event => {
    stats[event.name]++;
  });
  
  // Send to analytics service
  analytics.sendBatch(stats);
  
  res.status(200).send('OK');
});

4. Lambda Processing

Problem: Need serverless event processing Solution:
{
  "webhooks": [
    {
      "url": "lambda://arn:aws:lambda:us-east-1:123:function:process-events",
      "event_types": ["channel_occupied", "member_added"]
    }
  ]
}
Result: Events processed serverlessly, no dedicated infrastructure

Best Practices

1. Enable Batching for High Volume

For high-traffic applications, always enable batching:
{
  "webhooks": {
    "batching": {
      "enabled": true,
      "duration": 50,
      "size": 50
    }
  }
}

2. Use Appropriate Batch Size

Small batch (10-20): Low latency, more HTTP requests Medium batch (50-100): Balanced latency and efficiency Large batch (100+): Maximum efficiency, higher latency

3. Secure Webhook Endpoints

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

function verifyWebhook(req, secret) {
  const signature = req.headers['x-sockudo-signature'];
  const body = JSON.stringify(req.body);
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  
  return signature === expected;
}

app.post('/webhook', (req, res) => {
  if (!verifyWebhook(req, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  res.status(200).send('OK');
});

4. Handle Retries Idempotently

Webhooks may be delivered multiple times:
app.post('/webhook', async (req, res) => {
  const { name, channel, time_ms } = req.body;
  
  // Use time_ms as idempotency key
  const key = `webhook:${channel}:${time_ms}`;
  
  // Check if already processed
  if (await cache.exists(key)) {
    return res.status(200).send('Already processed');
  }
  
  // Process event
  await processEvent(req.body);
  
  // Mark as processed (TTL: 24 hours)
  await cache.set(key, '1', 86400);
  
  res.status(200).send('OK');
});

5. Monitor Webhook Health

Track webhook delivery success/failure:
app.post('/webhook', async (req, res) => {
  try {
    await processEvent(req.body);
    metrics.increment('webhook.success');
    res.status(200).send('OK');
  } catch (error) {
    metrics.increment('webhook.error');
    console.error('Webhook error:', error);
    res.status(500).send('Error');
  }
});

Troubleshooting

Webhooks Not Being Sent

Check 1: Are webhooks configured?
grep -A 10 "webhooks" config/config.json
Check 2: Are event types correct?
{
  "event_types": ["channel_occupied"]  // Check spelling
}
Check 3: Check Sockudo logs
grep "webhook" sockudo.log

Webhook Endpoint Not Receiving Events

Check 1: Is endpoint accessible?
curl -X POST https://example.com/webhook \
  -H "Content-Type: application/json" \
  -d '{"test": true}'
Check 2: Check firewall rules Check 3: Verify SSL certificate (if HTTPS)

High Webhook Latency

Symptom: Webhooks taking seconds to arrive Solution 1: Reduce batch duration
{
  "webhooks": {
    "batching": {
      "duration": 10  // Reduce from 50ms
    }
  }
}
Solution 2: Disable batching for low-traffic apps
{
  "webhooks": {
    "batching": {
      "enabled": false
    }
  }
}

Lambda Invocation Failures

Check 1: Lambda ARN correct?
{
  "url": "lambda://arn:aws:lambda:us-east-1:123456789:function:name"
}
Check 2: IAM permissions configured?
aws lambda get-policy --function-name my-webhook
Check 3: Lambda timeout sufficient?
aws lambda update-function-configuration \
  --function-name my-webhook \
  --timeout 30

Next Steps

Presence Channels

Track online users with presence channels

Delta Compression

Reduce bandwidth usage by 60-90%