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
| Event | Description |
|---|---|
message.received | New message from contact |
message.sent | Message sent to contact |
message.delivered | Message delivered confirmation |
message.read | Contact read a message |
conversation.created | New conversation started |
conversation.updated | Conversation details changed |
conversation.resolved | Conversation marked as resolved |
conversation.assigned | Conversation assigned to agent |
contact.created | New contact added |
contact.updated | Contact details changed |
contact.deleted | Contact 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
- Extract timestamp and signature from headers
- Construct signed payload:
{timestamp}.{body} - Calculate HMAC-SHA256 with your signing secret
- Compare signatures (use constant-time comparison)
- 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
- Return 200 quickly - Process async if needed
- Handle duplicates - Use event ID for idempotency
- Log everything - For debugging
- 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:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 2 minutes |
| 4 | 4 minutes |
| 5 | 8 minutes |
| 6 | 16 minutes |
| 7 | 32 minutes |
| 8 | 2 hours |
Response Code Handling
| Status | Action |
|---|---|
| 2xx | Success - no retry |
| 400-403 | Client error - no retry |
| 429 | Rate limited - retry |
| 5xx | Server error - retry |
| Timeout | Retry |
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
- Go to webhook.site
- Copy your unique URL
- Register it as a webhook
- View incoming events in real-time