Skip to main content

Webhooks

Receive real-time notifications when events happen in HueChat.

Overview

Instead of polling the API for changes, webhooks push events to your server as they happen.

Customer sends message


┌───────────────────┐
│ HueChat API │
└─────────┬─────────┘
│ POST

┌───────────────────┐
│ Your Server │
│ /webhooks/huechat │
└───────────────────┘

Supported Events

EventDescription
message.receivedNew message from contact
message.sentMessage sent to contact
message.deliveredMessage delivered confirmation
message.readContact read a message
conversation.createdNew conversation started
conversation.updatedConversation details changed
conversation.resolvedConversation marked as resolved
conversation.assignedConversation assigned to agent
contact.createdNew contact added
contact.updatedContact details changed
contact.deletedContact removed

Creating a Webhook

curl -X POST https://api.huechat.ai/v2/webhooks \
-H "X-API-Key: sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/huechat",
"events": ["message.received", "conversation.created"],
"active": true
}'

Response

{
"id": "wh_abc123xyz",
"url": "https://your-server.com/webhooks/huechat",
"events": ["message.received", "conversation.created"],
"active": true,
"retry_policy": "exponential",
"signing_secret": "whsec_1234567890abcdef",
"created_at": "2026-01-25T10:00:00Z"
}
warning

Save the signing_secret - you'll need it to verify webhook signatures.

Payload Format

All webhook payloads follow this structure:

{
"id": "evt_abc123xyz",
"type": "message.received",
"timestamp": "2026-01-25T16:45:00Z",
"retry_count": 0,
"data": {
// Event-specific data
},
"organization_id": "org_12345"
}

message.received

{
"id": "evt_msg_received_abc123",
"type": "message.received",
"timestamp": "2026-01-25T16:45:00Z",
"data": {
"id": "msg_xyz123",
"conversation_id": "conv_abc123",
"contact_id": "cnt_1a2b3c",
"channel": "whatsapp",
"message_type": "text",
"content": "Hello! I have a question about my order",
"sent_by": "contact",
"received_at": "2026-01-25T16:45:00Z",
"contact": {
"id": "cnt_1a2b3c",
"full_name": "John Doe",
"phone": "+1234567890"
},
"conversation": {
"id": "conv_abc123",
"status": "open",
"assigned_to": null
}
}
}

conversation.created

{
"id": "evt_conv_created_def456",
"type": "conversation.created",
"timestamp": "2026-01-25T17:00:00Z",
"data": {
"id": "conv_new123",
"contact_id": "cnt_xyz789",
"channel": "whatsapp",
"status": "open",
"contact": {
"id": "cnt_xyz789",
"full_name": "Jane Smith",
"phone": "+1987654321"
},
"first_message": "Hi, I need help with my account"
}
}

Verifying Signatures

All webhooks are signed with HMAC-SHA256. Always verify signatures to ensure requests are from HueChat.

Headers

X-HueChat-Signature: sha256=abc123xyz...
X-HueChat-Timestamp: 1706198700

Verification Process

  1. Extract timestamp and signature from headers
  2. Construct signed payload: {timestamp}.{body}
  3. Calculate HMAC-SHA256 with your signing secret
  4. Compare signatures (use constant-time comparison)
  5. Verify timestamp is within 5 minutes

Node.js Example

const crypto = require('crypto');

function verifyWebhook(request) {
const signature = request.headers['x-huechat-signature'];
const timestamp = request.headers['x-huechat-timestamp'];
const body = request.rawBody; // Raw string body

// Check timestamp (prevent replay attacks)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return false;
}

// Calculate expected signature
const signed = `${timestamp}.${body}`;
const expected = crypto
.createHmac('sha256', process.env.HUECHAT_WEBHOOK_SECRET)
.update(signed)
.digest('hex');

// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(signature.replace('sha256=', '')),
Buffer.from(expected)
);
}

// Express middleware
app.post('/webhooks/huechat', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyWebhook(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}

const event = JSON.parse(req.body);
// Process event...

res.status(200).send();
});

Python Example

import hmac
import hashlib
import time

def verify_webhook(request):
signature = request.headers.get('X-HueChat-Signature')
timestamp = request.headers.get('X-HueChat-Timestamp')
body = request.get_data(as_text=True)

# Check timestamp
now = int(time.time())
if abs(now - int(timestamp)) > 300:
return False

# Calculate expected signature
signed = f'{timestamp}.{body}'
expected = hmac.new(
os.environ['HUECHAT_WEBHOOK_SECRET'].encode(),
signed.encode(),
hashlib.sha256
).hexdigest()

# Constant-time comparison
return hmac.compare_digest(
signature.replace('sha256=', ''),
expected
)

@app.route('/webhooks/huechat', methods=['POST'])
def handle_webhook():
if not verify_webhook(request):
return jsonify({'error': 'Invalid signature'}), 401

event = request.get_json()
# Process event...

return '', 200

Handling Webhooks

Best Practices

  1. Return 200 quickly - Process async if needed
  2. Handle duplicates - Use event ID for idempotency
  3. Log everything - For debugging
  4. Verify signatures - Always!

Quick Response Pattern

app.post('/webhooks/huechat', async (req, res) => {
// Verify signature
if (!verifySignature(req)) {
return res.status(401).send();
}

const event = req.body;

// Queue for async processing
await queue.add('process-webhook', event);

// Return immediately
res.status(200).json({ received: true });
});

// Process asynchronously
queue.process('process-webhook', async (job) => {
const event = job.data;

switch (event.type) {
case 'message.received':
await handleNewMessage(event.data);
break;
case 'conversation.created':
await handleNewConversation(event.data);
break;
}
});

Retry Policy

Failed deliveries are retried with exponential backoff:

AttemptDelay
1Immediate
21 minute
32 minutes
44 minutes
58 minutes
616 minutes
732 minutes
82 hours

Response Code Handling

StatusAction
2xxSuccess - no retry
400-403Client error - no retry
429Rate limited - retry
5xxServer error - retry
TimeoutRetry

Managing Webhooks

List Webhooks

curl https://api.huechat.ai/v2/webhooks \
-H "X-API-Key: sk_live_your_key"

Update Webhook

curl -X PUT https://api.huechat.ai/v2/webhooks/wh_abc123 \
-H "X-API-Key: sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"events": ["message.received", "message.sent"],
"active": true
}'

Delete Webhook

curl -X DELETE https://api.huechat.ai/v2/webhooks/wh_abc123 \
-H "X-API-Key: sk_live_your_key"

Test Webhook

curl -X POST https://api.huechat.ai/v2/webhooks/wh_abc123/test \
-H "X-API-Key: sk_live_your_key"

Development Tips

Local Testing with ngrok

# Terminal 1: Start your server
npm run dev

# Terminal 2: Create tunnel
ngrok http 3000

# Use the ngrok URL for your webhook
# https://abc123.ngrok.io/webhooks/huechat

Webhook.site for Debugging

  1. Go to webhook.site
  2. Copy your unique URL
  3. Register it as a webhook
  4. View incoming events in real-time