Skip to main content

Webhooks

Webhooks allow you to receive real-time notifications when events occur in ClawBook. Instead of polling the API, configure a webhook URL and ClawBook will send HTTP POST requests to your server.

Setting Up Webhooks

Via Dashboard

  1. Go to SettingsAPIWebhooks
  2. Click Add Webhook
  3. Enter your endpoint URL
  4. Select events to subscribe to
  5. Click Create

Via API

curl -X POST "https://your-domain.com/api/v1/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"events": ["message.received", "message.sent", "integration.disconnected"],
"secret": "your-webhook-secret"
}'

Webhook Events

Message Events

EventDescription
message.receivedNew inbound message received
message.sentOutbound message sent
message.deliveredMessage delivery confirmed
message.failedMessage delivery failed

Integration Events

EventDescription
integration.connectedPlatform connected
integration.disconnectedPlatform disconnected
integration.errorIntegration error occurred

AI Events

EventDescription
ai.requestAI request sent
ai.responseAI response received
ai.errorAI provider error
ai.rate_limitedRate limit hit

System Events

EventDescription
system.healthHealth status change
backup.completedBackup finished
backup.failedBackup failed

Webhook Payload

All webhooks follow this structure:

{
"id": "evt_abc123",
"type": "message.received",
"created_at": "2026-01-30T14:30:00Z",
"data": {
// Event-specific data
}
}

Example: message.received

{
"id": "evt_abc123",
"type": "message.received",
"created_at": "2026-01-30T14:30:00Z",
"data": {
"message_id": "msg_xyz789",
"conversation_id": "conv_abc456",
"platform": "telegram",
"sender": {
"id": "123456789",
"name": "John Doe",
"username": "@johndoe"
},
"content": "Hello, how are you?",
"timestamp": "2026-01-30T14:29:58Z"
}
}

Example: message.sent

{
"id": "evt_def456",
"type": "message.sent",
"created_at": "2026-01-30T14:30:02Z",
"data": {
"message_id": "msg_response123",
"conversation_id": "conv_abc456",
"platform": "telegram",
"recipient": {
"id": "123456789",
"name": "John Doe"
},
"content": "I'm doing well, thank you for asking!",
"ai_model": "claude-3-5-sonnet",
"tokens": {
"input": 45,
"output": 15
},
"latency_ms": 1250
}
}

Example: integration.disconnected

{
"id": "evt_ghi789",
"type": "integration.disconnected",
"created_at": "2026-01-30T14:35:00Z",
"data": {
"platform": "whatsapp",
"reason": "Phone went offline",
"last_connected_at": "2026-01-30T12:00:00Z",
"auto_reconnect": true
}
}

Webhook Security

Verifying Signatures

All webhooks include a signature header for verification:

X-ClawBook-Signature: sha256=abc123...

Verify in your code:

Node.js:

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

return `sha256=${expected}` === signature;
}

// In your handler
app.post('/webhook', (req, res) => {
const signature = req.headers['x-clawbook-signature'];
const payload = JSON.stringify(req.body);

if (!verifySignature(payload, signature, 'your-webhook-secret')) {
return res.status(401).send('Invalid signature');
}

// Process webhook
console.log('Event:', req.body.type);
res.status(200).send('OK');
});

Python:

import hmac
import hashlib

def verify_signature(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return f"sha256={expected}" == signature

# In your handler
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-ClawBook-Signature')
payload = request.get_data(as_text=True)

if not verify_signature(payload, signature, 'your-webhook-secret'):
return 'Invalid signature', 401

event = request.json
print(f"Event: {event['type']}")
return 'OK', 200

PHP:

function verifySignature($payload, $signature, $secret) {
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}

// In your handler
$signature = $_SERVER['HTTP_X_CLAWBOOK_SIGNATURE'];
$payload = file_get_contents('php://input');

if (!verifySignature($payload, $signature, 'your-webhook-secret')) {
http_response_code(401);
exit('Invalid signature');
}

$event = json_decode($payload, true);
error_log("Event: " . $event['type']);
http_response_code(200);

IP Allowlisting

ClawBook webhooks come from these IP ranges:

203.0.113.0/24
198.51.100.0/24

Configure your firewall to only accept webhook requests from these IPs.

Webhook Delivery

Retry Policy

Failed webhooks are retried with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry12 hours

After 5 failures, the webhook is marked as failed and you'll receive an email notification.

Expected Response

Your endpoint should:

  • Return 200 OK within 30 seconds
  • Return 2xx status codes for success
  • Return 4xx for permanent failures (won't retry)
  • Return 5xx for temporary failures (will retry)

Timeout

Webhooks timeout after 30 seconds. For long-running tasks:

  1. Return 200 OK immediately
  2. Process asynchronously
  3. Use the API to acknowledge later if needed

Managing Webhooks

List Webhooks

curl "https://your-domain.com/api/v1/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY"

Get Webhook Details

curl "https://your-domain.com/api/v1/webhooks/wh_abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Response includes recent delivery attempts:

{
"id": "wh_abc123",
"url": "https://your-server.com/webhook",
"events": ["message.received", "message.sent"],
"status": "active",
"recent_deliveries": [
{
"id": "del_xyz789",
"event_id": "evt_abc123",
"status": "success",
"response_code": 200,
"response_time_ms": 150,
"timestamp": "2026-01-30T14:30:00Z"
}
]
}

Update Webhook

curl -X PATCH "https://your-domain.com/api/v1/webhooks/wh_abc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": ["message.received"],
"enabled": true
}'

Delete Webhook

curl -X DELETE "https://your-domain.com/api/v1/webhooks/wh_abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Test Webhook

Send a test event to your endpoint:

curl -X POST "https://your-domain.com/api/v1/webhooks/wh_abc123/test" \
-H "Authorization: Bearer YOUR_API_KEY"

Webhook Logs

View delivery logs in dashboard:

SettingsAPIWebhooks → Select webhook → Delivery Logs

Or via API:

curl "https://your-domain.com/api/v1/webhooks/wh_abc123/deliveries" \
-H "Authorization: Bearer YOUR_API_KEY"

Best Practices

  1. Always verify signatures - Prevent spoofed requests
  2. Respond quickly - Return 200 before processing
  3. Handle duplicates - Use event IDs for idempotency
  4. Monitor delivery - Check for failed deliveries
  5. Use HTTPS - Secure your endpoint
  6. Implement retry logic - In case you miss events

Example: Full Webhook Handler

Node.js/Express:

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.CLAWBOOK_WEBHOOK_SECRET;
const processedEvents = new Set();

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

app.post('/webhook/clawbook', (req, res) => {
// Verify signature
const signature = req.headers['x-clawbook-signature'];
if (!verifySignature(JSON.stringify(req.body), signature)) {
return res.status(401).send('Invalid signature');
}

const event = req.body;

// Idempotency check
if (processedEvents.has(event.id)) {
return res.status(200).send('Already processed');
}
processedEvents.add(event.id);

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

// Process asynchronously
processEvent(event).catch(console.error);
});

async function processEvent(event) {
switch (event.type) {
case 'message.received':
console.log('New message from:', event.data.sender.name);
// Your logic here
break;

case 'integration.disconnected':
console.log('Platform disconnected:', event.data.platform);
// Send alert
break;

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

app.listen(3000);

Next Steps