Webhook Setup Guide
Real-time email notifications via webhooks
Webhook Setup Guide
Webhooks provide real-time notifications when events occur in your daimon.email account, eliminating the need for constant polling and reducing latency for your agents.
Why Use Webhooks?
Real-Time Delivery
Receive notifications within milliseconds of email arrival. No polling delays.
Reduced Latency
Agents respond instantly to emails instead of waiting for the next poll cycle.
Lower Costs
Fewer API calls = lower bandwidth and compute costs compared to polling.
Event-Driven Architecture
Build reactive systems that respond to email events as they happen.
Webhook Events
daimon.email webhooks deliver these event types:
| Event | Description | Availability |
|---|---|---|
message.received | New email received in an inbox | All tiers |
message.bounced | Outbound email bounced (permanent failure) | Paid tiers |
message.complaint | Recipient marked email as spam | Paid tiers |
account.upgraded | Account upgraded to paid tier | All tiers |
webhook.unhealthy | Webhook endpoint failing health checks | All tiers |
Note
See Webhook Event Reference for complete payload examples.
Creating a Webhook
Step 1: Set Up Endpoint
Your webhook endpoint must:
- Accept
POSTrequests - Use
HTTPS(required for security) - Return
200 OKwithin 5 seconds - Verify HMAC signature (see Verifying HMAC)
import express from 'express';
import crypto from 'crypto';
const app = express();
app.post('/webhook/daimon', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-daimon-signature'] as string;
const secret = process.env.WEBHOOK_SECRET!;
// Verify HMAC signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
if (signature !== expectedSignature) {
console.error('Invalid signature');
return res.status(401).send('Unauthorized');
}
// Parse payload
const payload = JSON.parse(req.body.toString());
// Handle event
if (payload.event === 'message.received') {
console.log(`New message: ${payload.message.subject}`);
// Process the message
}
// Acknowledge receipt
res.status(200).send('OK');
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});from flask import Flask, request
import hmac
import hashlib
import os
import json
app = Flask(__name__)
@app.route('/webhook/daimon', methods=['POST'])
def webhook():
signature = request.headers.get('X-Daimon-Signature')
secret = os.environ['WEBHOOK_SECRET'].encode()
# Verify HMAC signature
expected_signature = hmac.new(
secret,
request.data,
hashlib.sha256
).hexdigest()
if signature != expected_signature:
print('Invalid signature')
return 'Unauthorized', 401
# Parse payload
payload = json.loads(request.data)
# Handle event
if payload['event'] == 'message.received':
print(f"New message: {payload['message']['subject']}")
# Process the message
# Acknowledge receipt
return 'OK', 200
if __name__ == '__main__':
app.run(port=3000)package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"os"
)
type WebhookPayload struct {
Event string `json:"event"`
Message map[string]interface{} `json:"message"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-Daimon-Signature")
secret := []byte(os.Getenv("WEBHOOK_SECRET"))
// Read body
body, _ := io.ReadAll(r.Body)
// Verify HMAC signature
mac := hmac.New(sha256.New, secret)
mac.Write(body)
expectedSignature := hex.EncodeToString(mac.Sum(nil))
if signature != expectedSignature {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Parse payload
var payload WebhookPayload
json.Unmarshal(body, &payload)
// Handle event
if payload.Event == "message.received" {
subject := payload.Message["subject"].(string)
println("New message:", subject)
// Process the message
}
// Acknowledge receipt
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
func main() {
http.HandleFunc("/webhook/daimon", webhookHandler)
http.ListenAndServe(":3000", nil)
}Warning
Never skip signature verification. Without it, anyone can send fake webhook events to your endpoint.
Step 2: Register Webhook
Create the webhook via API using your account API key (not inbox API key):
curl -X POST https://api.daimon.email/v1/webhooks \
-H "Authorization: Bearer dm_live_account123..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-platform.com/webhook/daimon",
"events": ["message.received", "message.bounced"],
"inbox_id": "inbox_abc123"
}'import { DaimonClient } from 'daimon-email';
const client = new DaimonClient({
apiKey: accountApiKey // Use account key, not inbox key
});
const webhook = await client.webhooks.create({
url: 'https://your-platform.com/webhook/daimon',
events: ['message.received', 'message.bounced'],
inboxId: 'inbox_abc123' // Optional: filter to specific inbox
});
console.log('Webhook created:', webhook.id);
console.log('Secret:', webhook.secret);
// Store the secret securely for signature verification
await secretsManager.store('webhook-secret', webhook.secret);from daimon_email import DaimonClient
client = DaimonClient(api_key=account_api_key)
webhook = client.webhooks.create(
url='https://your-platform.com/webhook/daimon',
events=['message.received', 'message.bounced'],
inbox_id='inbox_abc123' # Optional: filter to specific inbox
)
print(f"Webhook created: {webhook.id}")
print(f"Secret: {webhook.secret}")
# Store the secret securely for signature verification
secrets_manager.store('webhook-secret', webhook.secret)Response
{
"result": {
"id": "wh_abc123",
"url": "https://your-platform.com/webhook/daimon",
"events": ["message.received", "message.bounced"],
"inbox_id": "inbox_abc123",
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"status": "active",
"created_at": "2026-03-16T10:30:00Z"
},
"next_steps": [
"Store the secret securely — it's only shown once",
"Use the secret to verify HMAC signatures on incoming webhooks",
"Test the webhook with POST /v1/webhooks/{id}/test"
]
}Warning
The webhook secret is only returned once during creation. Store it securely in your secrets manager. If you lose it, you'll need to rotate the webhook secret via POST /v1/webhooks/{id}/rotate-secret.
Step 3: Test Your Webhook
Send a test event to verify your endpoint is working:
curl -X POST https://api.daimon.email/v1/webhooks/wh_abc123/test \
-H "Authorization: Bearer dm_live_account123..."const testResult = await client.webhooks.test('wh_abc123');
console.log('Test status:', testResult.status);
console.log('Response code:', testResult.responseCode);
console.log('Latency:', testResult.latencyMs, 'ms');test_result = client.webhooks.test('wh_abc123')
print(f"Test status: {test_result.status}")
print(f"Response code: {test_result.response_code}")
print(f"Latency: {test_result.latency_ms} ms")Response
{
"result": {
"status": "success",
"response_code": 200,
"response_body": "OK",
"latency_ms": 123,
"tested_at": "2026-03-16T10:35:00Z"
}
}If the test fails, check:
- Is your endpoint reachable via HTTPS?
- Does it return
200 OKwithin 5 seconds? - Is HMAC signature verification implemented correctly?
Webhook Filtering
Filter by Inbox
Limit webhook events to a specific inbox:
const webhook = await client.webhooks.create({
url: 'https://your-platform.com/webhook/daimon',
events: ['message.received'],
inboxId: 'inbox_abc123' // Only receive events for this inbox
});Without inboxId, the webhook receives events for all inboxes in your account.
Filter by Event Type
Subscribe to only the events you care about:
const webhook = await client.webhooks.create({
url: 'https://your-platform.com/webhook/daimon',
events: [
'message.received', // New emails
'message.bounced' // Bounces only
]
// Ignores: message.complaint, account.upgraded, webhook.unhealthy
});Info
You can create multiple webhooks with different event filters and inbox filters.
Webhook Health Monitoring
daimon.email monitors webhook health automatically and marks webhooks as unhealthy after consecutive failures.
Health Check Criteria
A webhook is marked unhealthy if:
- 5 consecutive requests return non-200 status codes
- 5 consecutive requests timeout (>5 seconds)
- 5 consecutive requests fail with network errors
When a Webhook Goes Unhealthy
- Webhook is paused: No more events are delivered
- Event is sent: You receive a
webhook.unhealthyevent - Retries stop: Failed events are dropped (not queued)
{
"event": "webhook.unhealthy",
"webhook_id": "wh_abc123",
"url": "https://your-platform.com/webhook/daimon",
"failure_count": 5,
"last_error": "Connection timeout after 5000ms",
"detected_at": "2026-03-16T11:00:00Z",
"next_steps": [
"Check your endpoint is reachable and returning 200 OK",
"Fix the issue and re-enable via POST /v1/webhooks/{id}/enable"
]
}Re-enabling an Unhealthy Webhook
Once you've fixed the issue:
curl -X POST https://api.daimon.email/v1/webhooks/wh_abc123/enable \
-H "Authorization: Bearer dm_live_account123..."Response
{
"result": {
"id": "wh_abc123",
"status": "active",
"health": "healthy",
"re_enabled_at": "2026-03-16T11:05:00Z"
}
}Retry Logic
daimon.email retries failed webhook deliveries with exponential backoff:
| Attempt | Delay | Total Time |
|---|---|---|
| 1 | Immediate | 0s |
| 2 | 5s | 5s |
| 3 | 15s | 20s |
| 4 | 45s | 1m 5s |
| 5 | 135s | 3m 20s |
After 5 failed attempts, the webhook is marked unhealthy and retries stop.
Note
To avoid losing events during downtime, implement a fallback polling mechanism or use a message queue (e.g., SQS, RabbitMQ) between daimon.email webhooks and your agent workers.
Best Practices
Verify Signatures
Always verify HMAC signatures. Never trust webhook payloads without verification.
Return 200 Quickly
Acknowledge receipt immediately. Process events asynchronously in a background job.
Use Idempotency Keys
Store event_id in your database to prevent duplicate processing if webhooks are retried.
Monitor Health
Set up alerts for webhook.unhealthy events. Monitor latency and error rates.
Example: Idempotent Event Processing
import { DaimonWebhookPayload } from 'daimon-email';
async function handleWebhook(payload: DaimonWebhookPayload) {
const eventId = payload.event_id;
// Check if we've already processed this event
const existing = await db.events.findOne({ eventId });
if (existing) {
console.log(`Event ${eventId} already processed, skipping`);
return; // Idempotent: safe to retry
}
// Process the event
if (payload.event === 'message.received') {
await processMessage(payload.message);
}
// Mark as processed
await db.events.create({ eventId, processedAt: new Date() });
}Managing Webhooks
List All Webhooks
curl -X GET https://api.daimon.email/v1/webhooks \
-H "Authorization: Bearer dm_live_account123..."Response
{
"result": {
"webhooks": [
{
"id": "wh_abc123",
"url": "https://your-platform.com/webhook/daimon",
"events": ["message.received"],
"status": "active",
"health": "healthy"
},
{
"id": "wh_def456",
"url": "https://backup.com/webhook",
"events": ["message.bounced", "message.complaint"],
"status": "paused",
"health": "unhealthy"
}
],
"total": 2
}
}Update Webhook
Change webhook URL or event filters:
curl -X PATCH https://api.daimon.email/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer dm_live_account123..." \
-H "Content-Type: application/json" \
-d '{
"events": ["message.received", "message.bounced", "message.complaint"]
}'Delete Webhook
curl -X DELETE https://api.daimon.email/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer dm_live_account123..."Warning
Deleting a webhook is permanent. You cannot recover the webhook secret after deletion.
Debugging Webhooks
View Webhook Logs
Check recent delivery attempts and failures:
curl -X GET https://api.daimon.email/v1/webhooks/wh_abc123/logs \
-H "Authorization: Bearer dm_live_account123..."Response
{
"result": {
"logs": [
{
"event_id": "evt_abc123",
"event_type": "message.received",
"delivered_at": "2026-03-16T10:40:00Z",
"response_code": 200,
"latency_ms": 89
},
{
"event_id": "evt_def456",
"event_type": "message.received",
"attempted_at": "2026-03-16T10:45:00Z",
"response_code": 500,
"error": "Internal Server Error",
"latency_ms": 1234,
"retry_count": 3
}
],
"total": 2
}
}Common Issues
Webhook returns 401 Unauthorized
Problem: HMAC signature verification is failing.
Solution:
- Verify you're using the correct webhook secret
- Check signature computation matches Verifying HMAC guide
- Ensure request body is used as-is (no parsing before verification)
Webhook times out after 5 seconds
Problem: Your endpoint is too slow.
Solution:
- Return
200 OKimmediately after receiving the webhook - Process events asynchronously in a background job queue
- Offload heavy work to workers (don't block the webhook handler)
Webhook delivers duplicate events
Problem: Retries are causing duplicate processing.
Solution:
- Store
event_idin your database - Check if
event_idexists before processing - Make your event handlers idempotent
Webhook goes unhealthy frequently
Problem: Endpoint is unreliable or slow.
Solution:
- Monitor endpoint uptime and latency
- Use a load balancer with health checks
- Scale your webhook handler horizontally
- Consider using a message queue (SQS, RabbitMQ) for buffering