Integrations
Webhooks
Webhooks
Webhooks enable real-time communication between HIVE Protocol and your external systems. When events occur in HIVE, webhook endpoints receive instant notifications, allowing you to build reactive integrations and automated workflows.
Overview
┌─────────────────────────────────────────────────────────────────┐
│ Webhook Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ HIVE Protocol Your System │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Event │ POST request │ Webhook │ │
│ │ Occurs │ ─────────────────▶ │ Endpoint │ │
│ │ │ with payload │ │ │
│ │ - message │ │ - Verify │ │
│ │ - swarm │ ◀───────────────── │ - Process │ │
│ │ - agent │ 200 OK │ - Act │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ Features: │
│ - HMAC signature verification │
│ - Automatic retry on failure │
│ - Event filtering │
│ - Delivery logging │
│ │
└─────────────────────────────────────────────────────────────────┘Setting Up Webhooks
Creating a Webhook
- Navigate to Settings > Webhooks
- Click Create Webhook
- Fill in the configuration:
| Field | Description | Required |
|---|---|---|
| Name | Descriptive identifier | Yes |
| URL | HTTPS endpoint URL | Yes |
| Events | Events to subscribe to | Yes |
| Secret | HMAC signing secret (auto-generated) | Yes |
| Active | Enable/disable webhook | Yes |
| Headers | Custom headers to include | No |
Configuration Interface
┌─────────────────────────────────────────────────────────────────┐
│ Create Webhook │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Name │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Production Slack Notifier │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Endpoint URL │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ https://api.yourcompany.com/webhooks/hive │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Events to Subscribe │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ [x] message.created [ ] message.updated │ │
│ │ [x] message.flagged [x] swarm.created │ │
│ │ [x] swarm.completed [ ] swarm.deleted │ │
│ │ [x] agent.error [ ] agent.created │ │
│ │ [x] tool.executed [ ] tool.error │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Signing Secret │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ whsec_a1b2c3d4e5f6... [Regenerate] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Custom Headers (Optional) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ X-Custom-Header: your-value │ │
│ │ Authorization: Bearer token123 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ [Cancel] [Create Webhook] │
└─────────────────────────────────────────────────────────────────┘Available Events
Message Events
| Event | Trigger | Payload Includes |
|---|---|---|
| message.created | New message sent | message_id, swarm_id, content, sender |
| message.updated | Message edited | message_id, old_content, new_content |
| message.deleted | Message removed | message_id, swarm_id |
| message.flagged | Message flagged for review | message_id, flag_reason, flagged_by |
Example: message.created payload
{
"id": "evt_msg_created_abc123",
"type": "message.created",
"created_at": "2024-01-15T10:30:00.000Z",
"data": {
"message_id": "msg_xyz789",
"swarm_id": "swm_123456",
"swarm_name": "Research Project",
"content": "I've completed the analysis of the quarterly data.",
"sender_type": "agent",
"sender_id": "agt_456",
"sender_name": "Data Analyst",
"metadata": {
"tokens_used": 245,
"model": "gpt-4o",
"latency_ms": 1234
}
}
}Swarm Events
| Event | Trigger | Payload Includes |
|---|---|---|
| swarm.created | New swarm created | swarm_id, name, created_by |
| swarm.updated | Swarm settings changed | swarm_id, changes |
| swarm.deleted | Swarm removed | swarm_id, deleted_by |
| swarm.completed | Swarm task finished | swarm_id, result, duration |
| swarm.agent_added | Agent joined swarm | swarm_id, agent_id |
| swarm.agent_removed | Agent left swarm | swarm_id, agent_id, reason |
Example: swarm.completed payload
{
"id": "evt_swarm_completed_def456",
"type": "swarm.completed",
"created_at": "2024-01-15T11:45:00.000Z",
"data": {
"swarm_id": "swm_123456",
"swarm_name": "Research Project",
"status": "completed",
"result": {
"summary": "Analysis complete with 15 insights generated",
"outputs": ["report.pdf", "data.csv"]
},
"duration_seconds": 3600,
"messages_count": 45,
"tokens_used": {
"input": 25000,
"output": 12000,
"total": 37000
},
"cost_usd": 0.45
}
}Agent Events
| Event | Trigger | Payload Includes |
|---|---|---|
| agent.created | New agent created | agent_id, name, model |
| agent.updated | Agent config changed | agent_id, changes |
| agent.deleted | Agent removed | agent_id, deleted_by |
| agent.error | Agent encountered error | agent_id, error, context |
| agent.status_changed | Agent status update | agent_id, old_status, new_status |
Example: agent.error payload
{
"id": "evt_agent_error_ghi789",
"type": "agent.error",
"created_at": "2024-01-15T12:00:00.000Z",
"data": {
"agent_id": "agt_456",
"agent_name": "Data Analyst",
"swarm_id": "swm_123456",
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "OpenAI rate limit exceeded",
"provider": "openai",
"model": "gpt-4o"
},
"context": {
"request_id": "req_abc123",
"retry_count": 3,
"last_message_id": "msg_xyz788"
}
}
}Tool Events
| Event | Trigger | Payload Includes |
|---|---|---|
| tool.executed | Tool successfully ran | tool_id, agent_id, result |
| tool.error | Tool execution failed | tool_id, agent_id, error |
Example: tool.executed payload
{
"id": "evt_tool_executed_jkl012",
"type": "tool.executed",
"created_at": "2024-01-15T12:15:00.000Z",
"data": {
"tool_id": "tool_weather_api",
"tool_name": "Weather API",
"agent_id": "agt_456",
"agent_name": "Data Analyst",
"swarm_id": "swm_123456",
"execution": {
"input": { "location": "San Francisco, CA" },
"output": { "temperature": 65, "conditions": "sunny" },
"duration_ms": 234
}
}
}User Events
| Event | Trigger | Payload Includes |
|---|---|---|
| user.joined | User added to account | user_id, email, role |
| user.removed | User removed | user_id, removed_by |
| user.role_changed | User role updated | user_id, old_role, new_role |
Webhook Payload Structure
Common Fields
All webhook payloads include these fields:
{
"id": "evt_unique_event_id",
"type": "event.type",
"created_at": "2024-01-15T10:30:00.000Z",
"api_version": "2024-01-01",
"data": {
// Event-specific data
}
}HTTP Headers
Every webhook request includes these headers:
| Header | Description | Example |
|---|---|---|
| Content-Type | Always JSON | application/json |
| X-Hive-Signature | HMAC-SHA256 signature | sha256=abc123... |
| X-Hive-Event | Event type | message.created |
| X-Hive-Delivery | Unique delivery ID | del_xyz789 |
| X-Hive-Timestamp | Unix timestamp | 1705313400 |
| User-Agent | HIVE webhook agent | HIVE-Webhook/1.0 |
Verifying Webhooks
Why Verify?
Always verify webhook signatures to ensure:
- Request actually came from HIVE
- Payload wasn't tampered with
- Protection against replay attacks
Signature Verification
HIVE signs webhooks using HMAC-SHA256:
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string,
timestamp: string
): boolean {
// Verify timestamp is recent (within 5 minutes)
const now = Math.floor(Date.now() / 1000);
const webhookTime = parseInt(timestamp, 10);
if (Math.abs(now - webhookTime) > 300) {
return false; // Reject old webhooks
}
// Compute expected signature
const signedPayload = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Compare signatures using timing-safe comparison
const expectedBuffer = Buffer.from(`sha256=${expected}`, 'utf8');
const signatureBuffer = Buffer.from(signature, 'utf8');
if (expectedBuffer.length !== signatureBuffer.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuffer, signatureBuffer);
}Complete Handler Examples
Node.js / Express:
import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.raw({ type: 'application/json' }));
const WEBHOOK_SECRET = process.env.HIVE_WEBHOOK_SECRET;
app.post('/webhooks/hive', (req, res) => {
const signature = req.headers['x-hive-signature'] as string;
const timestamp = req.headers['x-hive-timestamp'] as string;
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET, timestamp)) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// Respond immediately
res.status(200).json({ received: true });
// Process asynchronously
const event = JSON.parse(payload);
processEvent(event).catch(console.error);
});
async function processEvent(event: any) {
console.log(`Processing event: ${event.type}`);
switch (event.type) {
case 'message.created':
await handleMessageCreated(event.data);
break;
case 'swarm.completed':
await handleSwarmCompleted(event.data);
break;
case 'agent.error':
await handleAgentError(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
}
async function handleMessageCreated(data: any) {
// Send to Slack
await fetch(SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `New message in ${data.swarm_name}: ${data.content.substring(0, 100)}...`
})
});
}
async function handleSwarmCompleted(data: any) {
// Log completion metrics
console.log(`Swarm ${data.swarm_name} completed in ${data.duration_seconds}s`);
}
async function handleAgentError(data: any) {
// Alert on errors
await sendPagerDutyAlert({
summary: `Agent error: ${data.error.message}`,
severity: 'warning',
source: 'HIVE Protocol'
});
}Python / Flask:
import hmac
import hashlib
import time
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('HIVE_WEBHOOK_SECRET')
def verify_signature(payload, signature, timestamp):
# Check timestamp freshness
if abs(time.time() - int(timestamp)) > 300:
return False
# Compute signature
signed_payload = f"{timestamp}.{payload}"
expected = 'sha256=' + hmac.new(
WEBHOOK_SECRET.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhooks/hive', methods=['POST'])
def handle_webhook():
payload = request.get_data(as_text=True)
signature = request.headers.get('X-Hive-Signature')
timestamp = request.headers.get('X-Hive-Timestamp')
if not verify_signature(payload, signature, timestamp):
return jsonify({'error': 'Invalid signature'}), 401
event = request.get_json()
# Process based on event type
if event['type'] == 'message.created':
handle_message(event['data'])
elif event['type'] == 'agent.error':
handle_error(event['data'])
return jsonify({'received': True}), 200Testing Webhooks
Built-in Test Feature
- Go to Settings > Webhooks
- Click on your webhook
- Click Send Test Event
- Select event type
- View delivery result
┌─────────────────────────────────────────────────────────────────┐
│ Test Webhook: Production Slack Notifier │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Select Event Type: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ message.created [v] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Test Payload Preview: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "id": "evt_test_123", │ │
│ │ "type": "message.created", │ │
│ │ "data": { ... } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ [Cancel] [Send Test] │
└─────────────────────────────────────────────────────────────────┘Local Development with ngrok
# Install ngrok
npm install -g ngrok
# Start your local server
npm run dev # Running on localhost:3000
# In another terminal, expose your local server
ngrok http 3000
# You'll see output like:
# Forwarding: https://abc123.ngrok.io -> http://localhost:3000
# Use https://abc123.ngrok.io/webhooks/hive as your webhook URLWebhook Delivery Logs
View recent deliveries in the dashboard:
┌─────────────────────────────────────────────────────────────────┐
│ Recent Deliveries │
├─────────────────────────────────────────────────────────────────┤
│ │
│ del_abc123 message.created 200 OK 234ms 2 min ago │
│ del_def456 swarm.completed 200 OK 156ms 5 min ago │
│ del_ghi789 agent.error 500 Error 2034ms 8 min ago [R] │
│ del_jkl012 message.created 200 OK 189ms 12 min ago │
│ │
│ [R] = Scheduled for retry │
│ │
│ Click on a delivery to see full request/response details │
│ │
└─────────────────────────────────────────────────────────────────┘Retry Behavior
Automatic Retries
HIVE automatically retries failed webhook deliveries:
| Attempt | Delay | Total Time |
|---|---|---|
| 1 | Immediate | 0 |
| 2 | 1 minute | 1 min |
| 3 | 5 minutes | 6 min |
| 4 | 30 minutes | 36 min |
| 5 | 2 hours | 2h 36m |
| 6 | 8 hours | 10h 36m |
Retry Conditions
Webhooks are retried when:
- HTTP status code is 5xx (server error)
- Connection timeout (30 seconds)
- Connection refused/reset
Webhooks are NOT retried when:
- HTTP status code is 2xx (success)
- HTTP status code is 4xx (client error, except 429)
- HTTP status code is 429 (retried with backoff)
Handling Retries
Make your handlers idempotent:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
async function handleEvent(event: any) {
// Check if already processed using event ID
const { data: existing } = await supabase
.from('processed_webhooks')
.select('id')
.eq('event_id', event.id)
.maybeSingle();
if (existing) {
console.log(`Event ${event.id} already processed, skipping`);
return;
}
// Process the event
await processEvent(event);
// Mark as processed
await supabase
.from('processed_webhooks')
.insert({ event_id: event.id, processed_at: new Date().toISOString() });
}Best Practices
1. Respond Quickly
Return 200 immediately, process asynchronously:
app.post('/webhook', (req, res) => {
// Verify signature first
if (!verifySignature(req)) {
return res.status(401).send('Unauthorized');
}
// Respond immediately
res.status(200).json({ received: true });
// Queue for background processing
queue.add('process-webhook', req.body);
});2. Use a Queue System
For high-volume webhooks:
import { Queue, Worker } from 'bullmq';
const webhookQueue = new Queue('webhooks');
// In your webhook handler
app.post('/webhook', async (req, res) => {
await webhookQueue.add('process', req.body);
res.status(200).send('OK');
});
// Worker processes events
new Worker('webhooks', async (job) => {
const event = job.data;
await processEvent(event);
});3. Implement Circuit Breakers
Protect downstream services:
import CircuitBreaker from 'opossum';
const slackBreaker = new CircuitBreaker(sendToSlack, {
timeout: 5000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
async function handleMessage(data: any) {
try {
await slackBreaker.fire(data);
} catch (error) {
console.error('Slack notification failed:', error);
// Queue for retry or fallback
}
}4. Log Everything
Maintain detailed logs for debugging:
function logWebhook(event: any, status: string, error?: Error) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
event_id: event.id,
event_type: event.type,
status,
error: error?.message,
processing_time_ms: Date.now() - startTime
}));
}Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| 401 Unauthorized | Invalid signature | Check webhook secret matches |
| Timeout | Slow processing | Respond immediately, process async |
| Duplicate events | Retry delivery | Implement idempotency |
| Missing events | Wrong event selection | Check webhook event subscriptions |
| SSL Error | Invalid certificate | Ensure valid HTTPS certificate |
Debugging Checklist
[ ] Webhook endpoint is HTTPS
[ ] Endpoint returns 200 within 30 seconds
[ ] Signature verification uses correct secret
[ ] Event types are correctly subscribed
[ ] Handler processes events idempotently
[ ] Logs capture all webhook activity
[ ] Error handling doesn't crash the serverRelated Documentation
- [Available Integrations](/docs/integrations/available-integrations): Overview of all integrations
- [Custom Tools](/docs/integrations/custom-tools): Build your own tools
- [API Reference](/docs/api/authentication): API documentation