Webhooks
Webhooks allow you to receive real-time notifications when events occur in DGuard. For security, webhooks use HMAC-SHA256 signatures instead of Bearer tokens to verify that DGuard is the legitimate sender.
Overview
Real-time Delivery
Events delivered within seconds of occurrence
Secure Signatures
HMAC-SHA256 signature verification
Automatic Retries
Failed deliveries retried with backoff
Limits and Constraints
The following limits apply to webhook delivery and configuration. Contact sales for Enterprise tier adjustments.
| Constraint | Sandbox | Production | Enterprise |
|---|---|---|---|
| Max Webhooks per Account | 10 | 50 | Unlimited |
| Max Payload Size | 256 KB | ||
| Delivery Rate (per endpoint) | 100 events/min | 1,000 events/min | 10,000 events/min |
| Max Concurrent Connections | 5 | 10 | 50 |
| Response Timeout | 30 seconds | ||
If your endpoint receives events faster than it can process them, events will be queued and delivered with increasing delay. If the queue grows too large, oldest events may be dropped to the Dead Letter Queue.
Setting Up Webhooks
Configure webhooks through the DGuard Dashboard or via the API. Each webhook endpoint can subscribe to specific event types.
/webhooksCreate Webhook Request
{
"url": "https://your-server.com/webhooks/dguard",
"events": [
"fraud.detected",
"fraud.resolved",
"refund.created",
"refund.completed",
"refund.failed"
],
"generate_secret": true,
"description": "Production fraud alerts",
"metadata": {
"environment": "production"
}
}Set generate_secret to true to have DGuard generate a secure HMAC-SHA256 secret for you. Alternatively, you can provide your own secret string (minimum 32 characters).
Response (201 Created)
{
"webhook_id": "wh_abc123xyz",
"url": "https://your-server.com/webhooks/dguard",
"events": [
"fraud.detected",
"fraud.resolved",
"refund.created",
"refund.completed",
"refund.failed"
],
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0...",
"status": "active",
"created_at": "2025-01-15T10:00:00Z"
}Webhook Management Endpoints
/webhooks/webhooks/webhooks/{webhook_id}/webhooks/{webhook_id}/status/webhooks/{webhook_id}/webhooks/{webhook_id}/webhooks/{webhook_id}/rotate-secret/webhooks/{webhook_id}/testProgrammatic Health Check
Monitor the health of your webhook integration programmatically using the status endpoint. This provides real-time metrics on delivery success rates and current endpoint availability.
{
"webhook_id": "wh_abc123xyz",
"status": "healthy",
"metrics": {
"last_24h_success_rate": 0.998,
"total_deliveries": 1452,
"failed_deliveries": 3,
"last_successful_delivery": "2025-01-19T16:45:12Z",
"average_latency_ms": 245
},
"last_failure": {
"timestamp": "2025-01-19T10:22:05Z",
"http_status": 503,
"error_message": "Service Unavailable"
}
}Webhook Events
Subscribe to specific events based on your integration needs. Each event type corresponds to a specific action or state change in the system.
Fraud Events
| Event | Description | Trigger |
|---|---|---|
| fraud.detected | Fraudulent transaction identified | Real-time detection flags a transaction |
| fraud.resolved | Fraud alert marked as resolved | Manual review or automated resolution |
| fraud.escalated | Fraud case escalated for review | Score threshold exceeded or pattern match |
| fraud.score_updated | Transaction fraud score recalculated | New data available for existing transaction |
Refund Events
| Event | Description | Trigger |
|---|---|---|
| refund.created | New refund request submitted | POST /refund/request called |
| refund.processing | Refund is being processed | Payment network processing started |
| refund.completed | Refund successfully completed | Funds credited to beneficiary |
| refund.failed | Refund processing failed | Payment network rejection |
| refund.cancelled | Refund cancelled before processing | POST /refund/{id}/cancel called |
| refund.voided | Refund administratively voided | Admin action or compliance review |
Security Events
| Event | Description | Trigger |
|---|---|---|
| darkweb.leak_detected | Credentials found on dark web | Monitoring detected exposed data |
| phishing.email_detected | Phishing email identified | Email scanning flagged threat |
| phishing.sms_detected | Phishing SMS identified | SMS scanning flagged threat |
| url.malicious_detected | Malicious URL identified | URL scan detected threat |
| spam.call_blocked | Spam call blocked | Call identified and blocked |
Delivery Guarantees
Understanding DGuard's delivery semantics is essential for building robust webhook handlers.
At-Least-Once Delivery
DGuard guarantees that every event will be delivered at least once. In rare cases (e.g., network partitions, retry scenarios), the same event may be delivered multiple times. Always implement idempotency using the X-DGuard-Event-ID header.
Event Ordering
Events are not guaranteed to arrive in strict chronological order across different event types. However, events of the same type for the same resource (e.g., multiple refund.* events for the same refund_id) are generally delivered in order.
Handling Out-of-Order Events
Use the created_at timestamp in the event payload to determine the true sequence. If you receive an event with an older timestamp than your current state, you may choose to ignore it or reconcile based on your business logic.
Webhook Payload Structure
All webhook payloads follow a consistent structure, making it easy to parse and route events in your application.
Example Payload (fraud.detected)
{
"id": "evt_abc123xyz",
"type": "fraud.detected",
"api_version": "2025-01-15",
"created_at": "2025-01-15T14:30:00Z",
"data": {
"object": "fraud_alert",
"transaction_id": "txn_abc123",
"user_id": "usr_123456",
"fraud_score": 0.92,
"fraud_level": "critical",
"amount": 5234.00,
"currency": "EUR",
"risk_factors": [
"Unusual transaction velocity",
"New device fingerprint",
"High-risk merchant category"
],
"recommendation": "deny",
"detected_at": "2025-01-15T14:29:58Z"
},
"metadata": {
"webhook_id": "wh_abc123xyz",
"delivery_attempt": 1
}
}Example Payload (refund.completed)
{
"id": "evt_def456xyz",
"type": "refund.completed",
"api_version": "2025-01-15",
"created_at": "2025-01-15T14:31:00Z",
"data": {
"object": "refund",
"refund_id": "ref_abc123xyz",
"transaction_id": "txn_abc123",
"amount": 5234.00,
"currency": "EUR",
"status": "completed",
"beneficiary": {
"name": "Juan García López",
"account_masked": "JO94****0302"
},
"completed_at": "2025-01-15T14:30:58Z",
"processing_time_seconds": 18,
"reference": "CLIQ-20250115-789012"
},
"metadata": {
"webhook_id": "wh_abc123xyz",
"delivery_attempt": 1
}
}Verifying Webhook Signatures
Security Critical
Always verify webhook signatures before processing events. Failing to verify signatures leaves your application vulnerable to spoofed events.
DGuard signs all webhook payloads using HMAC-SHA256 with your webhook secret. The signature is included in the X-DGuard-Signature header.
Webhook Headers
| Header | Description |
|---|---|
| X-DGuard-Signature | HMAC-SHA256 signature of the payload |
| X-DGuard-Timestamp | Unix timestamp when the webhook was sent |
| X-DGuard-Event-ID | Unique event identifier for deduplication |
| X-DGuard-Webhook-ID | ID of the webhook endpoint |
Signature Verification (Node.js)
import crypto from 'crypto';
interface WebhookHeaders {
'x-dguard-signature': string;
'x-dguard-timestamp': string;
'x-dguard-event-id': string;
}
function verifyWebhookSignature(
payload: string,
headers: WebhookHeaders,
secret: string,
toleranceSeconds: number = 60 // 60 seconds maximum
): boolean {
const signature = headers['x-dguard-signature'];
const timestamp = headers['x-dguard-timestamp'];
// 1. Check timestamp to prevent replay attacks
const currentTime = Math.floor(Date.now() / 1000);
const webhookTime = parseInt(timestamp, 10);
if (Math.abs(currentTime - webhookTime) > toleranceSeconds) {
console.error('Webhook timestamp outside 60s tolerance window');
return false;
}
// 2. Compute expected signature
// Signature is computed over: timestamp + '.' + payload
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// 3. Compare signatures using timing-safe comparison
const signatureBuffer = Buffer.from(signature, 'hex');
const expectedBuffer = Buffer.from(expectedSignature, 'hex');
if (signatureBuffer.length !== expectedBuffer.length) {
return false;
}
return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);
}
// Usage in Express.js
app.post('/webhooks/dguard', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body.toString();
const headers = req.headers as unknown as WebhookHeaders;
if (!verifyWebhookSignature(payload, headers, process.env.DGUARD_WEBHOOK_SECRET!)) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}
// Process the verified webhook
const event = JSON.parse(payload);
console.log('Received verified event:', event.type);
// Always respond quickly with 200
res.status(200).send('OK');
// Process event asynchronously
processWebhookEvent(event);
});Signature Verification (Python)
import hmac
import hashlib
import time
def verify_webhook_signature(
payload: bytes,
signature: str,
timestamp: str,
secret: str,
tolerance_seconds: int = 60
) -> bool:
# 1. Check timestamp to prevent replay attacks
current_time = int(time.time())
webhook_time = int(timestamp)
if abs(current_time - webhook_time) > tolerance_seconds:
print("Webhook timestamp outside 60s tolerance window")
return False
# 2. Compute expected signature
signed_payload = f"{timestamp}.{payload.decode('utf-8')}"
expected_signature = hmac.new(
secret.encode('utf-8'),
signed_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# 3. Compare signatures using timing-safe comparison
return hmac.compare_digest(signature, expected_signature)
# Usage in Flask
@app.route('/webhooks/dguard', methods=['POST'])
def handle_webhook():
payload = request.get_data()
signature = request.headers.get('X-DGuard-Signature')
timestamp = request.headers.get('X-DGuard-Timestamp')
if not verify_webhook_signature(payload, signature, timestamp, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = json.loads(payload)
print(f"Received verified event: {event['type']}")
# Process event asynchronously (use Celery, etc.)
process_webhook_event.delay(event)
return 'OK', 200Why These Security Measures?
Timestamp Validation (60s tolerance)
Prevents replay attacks where an attacker intercepts a legitimate webhook and resends it later. Without timestamp checks, old webhooks could be replayed indefinitely.
Constant-Time Comparison
Prevents timing attacks where an attacker measures response times to deduce the secret character-by-character. Always use crypto.timingSafeEqual() or equivalent.
On Verification Failure
- • Return
401 Unauthorizedimmediately - • Log the failed attempt with IP address and headers (for forensics)
- • Consider alerting your security team on repeated failures
- • Do not process the event or return detailed error messages
Common Pitfalls
Reading request body twice
Use express.raw() middleware or buffer the body once. Many frameworks consume the stream on first read.
Encoding mismatches
Ensure payload is treated as UTF-8 bytes. Different encodings produce different signatures.
Whitespace modifications
Do not parse/re-stringify JSON before verification. Verify the raw bytes exactly as received.
Missing timestamp in signed payload
The signature covers 'timestamp.payload', not just the payload. Include the dot separator.
Retry Logic
If your endpoint fails to respond with a 2xx status code, DGuard will retry the delivery using exponential backoff.
Retry Schedule
If an event fails after 10 attempts (approximately 28 hours), it is moved to a Dead Letter Queue (DLQ) and recorded in your event log for manual inspection.
What Counts as a Failure?
No response within 30 seconds
Server error responses
DNS failure, connection refused, TLS errors
Client errors
Endpoint explicitly disabled
Deduplication Strategy
Due to at-least-once delivery semantics, your webhook handler must be idempotent. Use the following strategy to safely handle duplicate events.
Key Principles
Store Event IDs for 7 Days Minimum
Persist each X-DGuard-Event-ID in your database or cache. Retries can occur up to 28 hours after initial delivery, but we recommend a 7-day retention window to handle edge cases.
Deduplication is Per-Endpoint
Event IDs are unique globally, but if you have multiple webhook endpoints, each will receive its own delivery. Deduplicate within each endpoint's handler, not across all endpoints.
Identical Payloads Guaranteed
If you receive the same event_id multiple times, the payload will always be identical. You can safely skip processing without comparing payloads.
Example: Redis-based Deduplication
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const DEDUP_TTL_SECONDS = 7 * 24 * 60 * 60; // 7 days
async function handleWebhook(eventId: string, payload: object): Promise<boolean> {
// Try to set the key only if it doesn't exist (NX)
const wasSet = await redis.set(
`webhook:processed:${eventId}`,
'1',
'EX', DEDUP_TTL_SECONDS,
'NX'
);
if (!wasSet) {
console.log(`Duplicate event ${eventId} - skipping`);
return false; // Already processed
}
// Process the event
await processEvent(payload);
return true;
}
// Usage in Express handler
app.post('/webhooks/dguard', async (req, res) => {
const eventId = req.headers['x-dguard-event-id'] as string;
// Always respond 200 immediately
res.status(200).send('OK');
// Process with deduplication
await handleWebhook(eventId, req.body);
});Best Practices
Respond Quickly
Return a 200 response within a maximum of 5 seconds. Process the event asynchronously using a job queue to avoid timeouts.
Implement Idempotency
Use the X-DGuard-Event-ID header to deduplicate events. Store processed event IDs and skip duplicates to handle retries safely.
Use HTTPS
Webhook URLs must use HTTPS with a valid SSL certificate. HTTP URLs and self-signed certificates are not supported.
Rotate Secrets Periodically
Rotate your webhook secret periodically using the POST /webhooks/{webhook_id}/rotate-secret endpoint. During rotation, both old and new secrets are valid for 24 hours.
Monitor Webhook Health
Webhooks with sustained high failure rates will be automatically disabled after 7 consecutive days of failures. DGuard provides multiple notification channels to prevent silent data loss:
webhook.disabled event sent to fallback endpoint (if configured)Re-enable: Via Dashboard or PATCH /webhooks/{webhook_id} with { "status": "active" }
Testing Webhooks
Use the test endpoint to send sample events to your webhook URL without affecting real data.
/webhooks/{webhook_id}/testRequest
{
"event_type": "fraud.detected"
}Sandbox Environment
In the sandbox environment, all events are test events by default. Use the metadata.test_mode: true field in production to identify test events.
Rate Limiting
Rate limits for Webhook management endpoints (creation, rotation, testing) are enforced per application. For detailed specifications, see the Security & Compliance page.