Webhooks are configured once, in the dashboard, and receive alerts from every bucket in the organization — the payload carries bucketId and dataSourceId so your application routes them. They are no longer registered per bucket.
For the current model, payload shape, signature verification and retry behaviour, see Webhook Delivery.
Webhooks Guide
Integrate dakkio with external services using webhooks to receive real-time notifications about events in your organization.
What are Webhooks?
Webhooks are HTTP callbacks that dakkio sends to your specified URL when events occur. They enable real-time integrations with:
- Slack, Discord, Microsoft Teams
- Email services (SendGrid, Mailgun)
- SMS providers (Twilio, Vonage)
- Custom applications
- Automation platforms (Zapier, Make)
Supported Events
| Event | Description | When Triggered |
|---|---|---|
alert.triggered | Alert rule condition met | Alert fires |
data.received | New data point ingested | Data arrives |
bucket.updated | Bucket configuration changed | Bucket modified |
Creating a Webhook
Via Dashboard
- Open Notifications
- Click "Add Webhook"
- Enter details:
- URL: Your webhook endpoint
- Events: Select events to receive
- Secret: (Optional) For signature verification
- Click "Create"
Via API
curl -X POST https://api.dakkio.io/api/webhooks \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"bucketId": "507f1f77bcf86cd799439011",
"url": "https://your-server.com/webhook",
"events": ["alert.triggered", "data.received"],
"secret": "your_webhook_secret"
}'
Webhook Payload
Alert Triggered Event
{
"event": "alert.triggered",
"timestamp": "2024-01-15T10:30:00Z",
"webhookId": "507f1f77bcf86cd799439016",
"bucketId": "507f1f77bcf86cd799439011",
"data": {
"alertId": "507f1f77bcf86cd799439015",
"alertName": "High Temperature Warning",
"dataSourceId": "507f1f77bcf86cd799439012",
"dataSourceName": "Temperature Sensor - Room A",
"condition": "Temperature exceeds 30°C for 5 minutes",
"currentValue": 31.5,
"threshold": 30,
"operator": ">",
"triggeredAt": "2024-01-15T10:30:00Z",
"dataPoint": {
"timestamp": "2024-01-15T10:30:00Z",
"values": {
"temperature": 31.5,
"humidity": 65
},
"metadata": {
"deviceId": "ESP32-001",
"location": "Room A"
}
}
}
}
Data Received Event
{
"event": "data.received",
"timestamp": "2024-01-15T10:30:00Z",
"webhookId": "507f1f77bcf86cd799439016",
"bucketId": "507f1f77bcf86cd799439011",
"data": {
"dataSourceId": "507f1f77bcf86cd799439012",
"dataSourceName": "Temperature Sensor - Room A",
"dataPoint": {
"_id": "507f1f77bcf86cd799439017",
"timestamp": "2024-01-15T10:30:00Z",
"values": {
"temperature": 22.5,
"humidity": 65
},
"metadata": {
"deviceId": "ESP32-001"
}
}
}
}
Bucket Updated Event
{
"event": "bucket.updated",
"timestamp": "2024-01-15T10:30:00Z",
"webhookId": "507f1f77bcf86cd799439016",
"bucketId": "507f1f77bcf86cd799439011",
"data": {
"changes": {
"retentionDays": {
"old": 30,
"new": 365
}
},
"updatedBy": "507f1f77bcf86cd799439010",
"updatedAt": "2024-01-15T10:30:00Z"
}
}
Security
Signature Verification
dakkio signs webhook requests using HMAC-SHA256. Always verify signatures to prevent spoofing.
Header:
X-Webhook-Signature: sha256=abc123def456...
Verification Examples:
Node.js
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = 'sha256=' + hmac.update(JSON.stringify(payload)).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}
// Express endpoint
app.post('/webhook', express.json(), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Process webhook
console.log('Event:', req.body.event);
res.status(200).send('OK');
});
Python
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
expected = 'sha256=' + hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask endpoint
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
secret = os.environ['WEBHOOK_SECRET']
payload = request.get_data(as_text=True)
if not verify_webhook_signature(payload, signature, secret):
return 'Invalid signature', 401
data = request.json
print(f"Event: {data['event']}")
return 'OK', 200
Integration Examples
Slack
Send alerts to Slack channel:
// Slack webhook URL from: https://api.slack.com/messaging/webhooks
const webhookUrl = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXX';
// Transform dakkio webhook to Slack message
app.post('/webhook', express.json(), (req, res) => {
const { event, data } = req.body;
if (event === 'alert.triggered') {
const message = {
text: `🚨 Alert: ${data.alertName}`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${data.alertName}*\n${data.condition}`
}
},
{
type: 'section',
fields: [
{
type: 'mrkdwn',
text: `*Current Value:*\n${data.currentValue}`
},
{
type: 'mrkdwn',
text: `*Threshold:*\n${data.threshold}`
},
{
type: 'mrkdwn',
text: `*Data Source:*\n${data.dataSourceName}`
},
{
type: 'mrkdwn',
text: `*Time:*\n${new Date(data.triggeredAt).toLocaleString()}`
}
]
}
]
};
axios.post(webhookUrl, message);
}
res.status(200).send('OK');
});
Discord
Send notifications to Discord:
// Discord webhook URL from: Server Settings > Integrations > Webhooks
const webhookUrl = 'https://discord.com/api/webhooks/123456/abcdef';
app.post('/webhook', express.json(), (req, res) => {
const { event, data } = req.body;
if (event === 'alert.triggered') {
const embed = {
embeds: [{
title: data.alertName,
description: data.condition,
color: 0xff0000, // Red
fields: [
{
name: 'Current Value',
value: data.currentValue.toString(),
inline: true
},
{
name: 'Threshold',
value: data.threshold.toString(),
inline: true
},
{
name: 'Data Source',
value: data.dataSourceName,
inline: false
}
],
timestamp: data.triggeredAt
}]
};
axios.post(webhookUrl, embed);
}
res.status(200).send('OK');
});
Email (SendGrid)
Send email notifications:
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
app.post('/webhook', express.json(), async (req, res) => {
const { event, data } = req.body;
if (event === 'alert.triggered') {
const msg = {
to: 'alerts@your-company.com',
from: 'noreply@your-company.com',
subject: `Alert: ${data.alertName}`,
html: `
<h2>${data.alertName}</h2>
<p><strong>Condition:</strong> ${data.condition}</p>
<p><strong>Current Value:</strong> ${data.currentValue}</p>
<p><strong>Threshold:</strong> ${data.threshold}</p>
<p><strong>Data Source:</strong> ${data.dataSourceName}</p>
<p><strong>Time:</strong> ${new Date(data.triggeredAt).toLocaleString()}</p>
`
};
await sgMail.send(msg);
}
res.status(200).send('OK');
});
SMS (Twilio)
Send SMS alerts:
const twilio = require('twilio');
const client = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
);
app.post('/webhook', express.json(), async (req, res) => {
const { event, data } = req.body;
if (event === 'alert.triggered') {
await client.messages.create({
body: `Alert: ${data.alertName} - Current value: ${data.currentValue}`,
from: process.env.TWILIO_PHONE_NUMBER,
to: '+1234567890'
});
}
res.status(200).send('OK');
});
Retry Logic
dakkio automatically retries failed webhook deliveries:
- Max Retries: 3 attempts
- Backoff: Exponential (1s, 2s, 4s)
- Timeout: 30 seconds per request
Configure retry behavior:
{
"retryConfig": {
"maxRetries": 3,
"backoffMultiplier": 2
}
}
Best Practices
1. Respond Quickly
// ✅ GOOD: Return 200 immediately, process async
app.post('/webhook', express.json(), (req, res) => {
res.status(200).send('OK');
// Process webhook in background
processWebhook(req.body).catch(console.error);
});
// ❌ BAD: Process synchronously, slow response
app.post('/webhook', express.json(), async (req, res) => {
await longRunningTask(req.body);
res.status(200).send('OK');
});
2. Always Verify Signatures
// ✅ GOOD: Verify before processing
if (!verifySignature(payload, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// ❌ BAD: No verification (security risk!)
app.post('/webhook', (req, res) => {
processWebhook(req.body);
});
3. Handle Idempotency
Webhooks may be delivered more than once. Use webhook ID to prevent duplicate processing:
const processedWebhooks = new Set();
app.post('/webhook', express.json(), (req, res) => {
const webhookId = req.body.webhookId;
if (processedWebhooks.has(webhookId)) {
return res.status(200).send('Already processed');
}
processWebhook(req.body);
processedWebhooks.add(webhookId);
res.status(200).send('OK');
});
4. Log Everything
app.post('/webhook', express.json(), (req, res) => {
console.log('Webhook received:', {
event: req.body.event,
webhookId: req.body.webhookId,
timestamp: req.body.timestamp
});
try {
processWebhook(req.body);
console.log('Webhook processed successfully');
} catch (error) {
console.error('Webhook processing failed:', error);
}
res.status(200).send('OK');
});
5. Use HTTPS
Always use HTTPS endpoints for webhooks:
✅ https://your-server.com/webhook
❌ http://your-server.com/webhook
Testing Webhooks
Local Testing with ngrok
# Install ngrok
npm install -g ngrok
# Start your webhook server
node server.js
# Expose to internet
ngrok http 3000
# Use ngrok URL in dakkio
https://abc123.ngrok.io/webhook
Manual Testing
Send test webhook payload:
curl -X POST https://your-server.com/webhook \
-H "Content-Type: application/json" \
-H "X-Webhook-Signature: sha256=test" \
-d '{
"event": "alert.triggered",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"alertName": "Test Alert",
"currentValue": 35,
"threshold": 30
}
}'
Monitoring Webhooks
Track webhook delivery status in the dashboard:
{
"stats": {
"totalDeliveries": 150,
"successfulDeliveries": 145,
"failedDeliveries": 5
},
"lastDeliveryAt": "2024-01-15T10:30:00Z",
"lastDeliveryStatus": "success"
}
Troubleshooting
Webhooks Not Receiving
- Check URL: Ensure it's publicly accessible
- Verify HTTPS: HTTP may be blocked
- Check firewall: Allow dakkio IPs
- Test manually: Send test request
- Check logs: Look for delivery attempts
Signature Verification Failing
- Check secret: Ensure it matches webhook configuration
- Raw body: Use raw request body, not parsed JSON
- Encoding: Use UTF-8 encoding
- Timing attacks: Use
timingSafeEqualin Node.js
Slow Webhook Processing
- Return 200 immediately: Process asynchronously
- Use message queue: Queue webhooks for processing
- Optimize processing: Reduce external API calls
- Scale horizontally: Add more webhook processors
Next Steps
- Alert Rules Guide - Set up alerts
- Examples - See webhook integrations
- Webhook Delivery - Current webhook model
Need Help?
- 📖 Webhook Delivery
- 🎮 Try in Playground
- 💬 Discord Community
- 📧 Email: support@dakkio.io