Webhooks
Webhooks enable real-time notifications when events occur in Deeployd. Instead of polling the API, receive HTTP callbacks when agents complete tasks, conversations end, or workflows finish.
Why Webhooks?
- Real-time updates: Know immediately when events happen
- Efficient: No polling required
- Reliable: Automatic retries on failure
- Secure: Signature verification
Creating Webhooks
From the Dashboard
- Go to Settings → Webhooks
- Click + Add Webhook
- Enter your endpoint URL
- Select events to subscribe to
- Click Create
Via API
const webhook = await client.webhooks.create({
url: 'https://your-app.com/webhooks/deeployd',
events: [
'conversation.completed',
'task.completed',
'task.failed',
'workflow.completed'
],
description: 'Production webhook'
});
console.log(webhook.id); // webhook-123
console.log(webhook.secret); // whsec_abc123...
Webhook Events
Conversation Events
| Event | Description |
|---|---|
conversation.created | New conversation started |
conversation.message | New message in conversation |
conversation.completed | Conversation ended |
conversation.error | Conversation error occurred |
Task Events
| Event | Description |
|---|---|
task.created | Task created |
task.started | Task execution began |
task.completed | Task finished successfully |
task.failed | Task failed |
task.cancelled | Task was cancelled |
Workflow Events
| Event | Description |
|---|---|
workflow.started | Workflow execution began |
workflow.completed | Workflow finished successfully |
workflow.failed | Workflow failed |
workflow.human_task | Human task awaiting response |
Agent Events
| Event | Description |
|---|---|
agent.created | Agent created |
agent.updated | Agent configuration changed |
agent.deleted | Agent deleted |
agent.tool_called | Agent called a tool |
Webhook Payload
Standard Format
{
"id": "evt_abc123",
"type": "task.completed",
"createdAt": "2024-01-15T10:00:00Z",
"data": {
"taskId": "task-456",
"agentId": "agent-789",
"status": "completed",
"output": {
"result": "Analysis complete",
"summary": "..."
},
"duration": 15000,
"tokensUsed": 1500
}
}
Event-Specific Data
conversation.completed
{
"type": "conversation.completed",
"data": {
"conversationId": "conv-123",
"agentId": "agent-456",
"userId": "user-789",
"messageCount": 12,
"duration": 180000,
"tokensUsed": 3500,
"endReason": "user_ended"
}
}
task.failed
{
"type": "task.failed",
"data": {
"taskId": "task-123",
"agentId": "agent-456",
"status": "failed",
"error": {
"code": "TIMEOUT",
"message": "Task exceeded maximum execution time"
},
"retryCount": 3,
"willRetry": false
}
}
workflow.human_task
{
"type": "workflow.human_task",
"data": {
"workflowId": "workflow-123",
"runId": "run-456",
"taskId": "human-task-789",
"prompt": "Please approve this expense report",
"approvers": ["manager@company.com"],
"expiresAt": "2024-01-16T10:00:00Z"
}
}
Signature Verification
All webhook requests include a signature for verification:
X-Deeployd-Signature: sha256=abc123...
X-Deeployd-Timestamp: 1705312200
Verifying Signatures
import crypto from 'crypto';
function verifyWebhook(
payload: string,
signature: string,
timestamp: string,
secret: string
): boolean {
// Check timestamp to prevent replay attacks
const now = Math.floor(Date.now() / 1000);
const ts = parseInt(timestamp);
if (Math.abs(now - ts) > 300) { // 5 minute tolerance
return false;
}
// Compute expected signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Compare signatures
const expected = `sha256=${expectedSignature}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express.js example
app.post('/webhooks/deeployd', (req, res) => {
const signature = req.headers['x-deeployd-signature'];
const timestamp = req.headers['x-deeployd-timestamp'];
const payload = JSON.stringify(req.body);
if (!verifyWebhook(payload, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const event = req.body;
console.log('Received:', event.type);
res.status(200).send('OK');
});
SDK Verification
import { verifyWebhookSignature } from '@deeployd/sdk';
const isValid = verifyWebhookSignature({
payload: req.body,
signature: req.headers['x-deeployd-signature'],
timestamp: req.headers['x-deeployd-timestamp'],
secret: WEBHOOK_SECRET
});
Retry Policy
Failed webhook deliveries are retried automatically:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 8 hours |
After 6 failed attempts, the webhook is marked as failed.
What Counts as Failure?
- HTTP status code >= 400
- Connection timeout (30 seconds)
- Network error
Responding to Webhooks
Return a 2xx status code quickly:
app.post('/webhooks/deeployd', async (req, res) => {
// Acknowledge receipt immediately
res.status(200).send('OK');
// Process asynchronously
processWebhookAsync(req.body);
});
Managing Webhooks
List Webhooks
const webhooks = await client.webhooks.list();
for (const webhook of webhooks.data) {
console.log({
id: webhook.id,
url: webhook.url,
events: webhook.events,
status: webhook.status
});
}
Update Webhook
await client.webhooks.update('webhook-123', {
events: ['task.completed', 'task.failed', 'workflow.completed'],
status: 'active'
});
Disable Webhook
await client.webhooks.update('webhook-123', {
status: 'disabled'
});
Delete Webhook
await client.webhooks.delete('webhook-123');
Rotate Secret
const webhook = await client.webhooks.rotateSecret('webhook-123');
console.log(webhook.secret); // New secret
Testing Webhooks
Send Test Event
await client.webhooks.test('webhook-123', {
eventType: 'task.completed'
});
Webhook Logs
View recent deliveries in the Dashboard:
- Go to Settings → Webhooks
- Click on a webhook
- View Delivery History
Each delivery shows:
- Timestamp
- Event type
- Response status
- Response body
- Duration
Local Development
Use a tunneling service for local development:
# Using ngrok
ngrok http 3000
# Your webhook URL becomes:
# https://abc123.ngrok.io/webhooks/deeployd
Best Practices
1. Verify Signatures
Always verify webhook signatures to prevent spoofing.
2. Respond Quickly
Return 200 immediately, process asynchronously:
// ✅ Good
res.status(200).send('OK');
await queue.add(processWebhook, event);
// ❌ Bad - blocks response
await processWebhook(event);
res.status(200).send('OK');
3. Handle Duplicates
Webhooks may be delivered multiple times. Use idempotency:
const eventId = event.id;
if (await alreadyProcessed(eventId)) {
return; // Skip duplicate
}
await processEvent(event);
await markAsProcessed(eventId);
4. Monitor Webhook Health
Set up alerts for:
- High failure rates
- Elevated latency
- Disabled webhooks
5. Use Specific Events
Subscribe only to events you need:
// ✅ Good - specific events
events: ['task.completed', 'task.failed']
// ❌ Bad - too broad
events: ['*']
Next: Explore Tasks for background job execution.