Skip to main content
Webhooks are organization-scoped

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

EventDescriptionWhen Triggered
alert.triggeredAlert rule condition metAlert fires
data.receivedNew data point ingestedData arrives
bucket.updatedBucket configuration changedBucket modified

Creating a Webhook

Via Dashboard

  1. Open Notifications
  2. Click "Add Webhook"
  3. Enter details:
    • URL: Your webhook endpoint
    • Events: Select events to receive
    • Secret: (Optional) For signature verification
  4. 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

  1. Check URL: Ensure it's publicly accessible
  2. Verify HTTPS: HTTP may be blocked
  3. Check firewall: Allow dakkio IPs
  4. Test manually: Send test request
  5. Check logs: Look for delivery attempts

Signature Verification Failing

  1. Check secret: Ensure it matches webhook configuration
  2. Raw body: Use raw request body, not parsed JSON
  3. Encoding: Use UTF-8 encoding
  4. Timing attacks: Use timingSafeEqual in Node.js

Slow Webhook Processing

  1. Return 200 immediately: Process asynchronously
  2. Use message queue: Queue webhooks for processing
  3. Optimize processing: Reduce external API calls
  4. Scale horizontally: Add more webhook processors

Next Steps

Need Help?