Webhooks Guide
Integrate dakkio with external services using webhooks to receive real-time notifications about events in your buckets.
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
- Navigate to your bucket
- Go to "Webhooks" tab
- Click "Create 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
- API Reference - Full webhook API
Need Help?
- 📖 API Reference
- 🎮 Try in Playground
- 💬 Discord Community
- 📧 Email: support@dakkio.io