DGuardAPI Docs

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.

ConstraintSandboxProductionEnterprise
Max Webhooks per Account1050Unlimited
Max Payload Size256 KB
Delivery Rate (per endpoint)100 events/min1,000 events/min10,000 events/min
Max Concurrent Connections51050
Response Timeout30 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.

POST/webhooks

Create Webhook Request

json
{
  "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)

json
{
  "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

POST/webhooks
GET/webhooks
GET/webhooks/{webhook_id}
GET/webhooks/{webhook_id}/status
PATCH/webhooks/{webhook_id}
DELETE/webhooks/{webhook_id}
POST/webhooks/{webhook_id}/rotate-secret
POST/webhooks/{webhook_id}/test

Programmatic 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.

json
{
  "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

EventDescriptionTrigger
fraud.detectedFraudulent transaction identifiedReal-time detection flags a transaction
fraud.resolvedFraud alert marked as resolvedManual review or automated resolution
fraud.escalatedFraud case escalated for reviewScore threshold exceeded or pattern match
fraud.score_updatedTransaction fraud score recalculatedNew data available for existing transaction

Refund Events

EventDescriptionTrigger
refund.createdNew refund request submittedPOST /refund/request called
refund.processingRefund is being processedPayment network processing started
refund.completedRefund successfully completedFunds credited to beneficiary
refund.failedRefund processing failedPayment network rejection
refund.cancelledRefund cancelled before processingPOST /refund/{id}/cancel called
refund.voidedRefund administratively voidedAdmin action or compliance review

Security Events

EventDescriptionTrigger
darkweb.leak_detectedCredentials found on dark webMonitoring detected exposed data
phishing.email_detectedPhishing email identifiedEmail scanning flagged threat
phishing.sms_detectedPhishing SMS identifiedSMS scanning flagged threat
url.malicious_detectedMalicious URL identifiedURL scan detected threat
spam.call_blockedSpam call blockedCall 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)

json
{
  "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)

json
{
  "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

HeaderDescription
X-DGuard-SignatureHMAC-SHA256 signature of the payload
X-DGuard-TimestampUnix timestamp when the webhook was sent
X-DGuard-Event-IDUnique event identifier for deduplication
X-DGuard-Webhook-IDID of the webhook endpoint

Signature Verification (Node.js)

typescript
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)

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', 200

Why 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 Unauthorized immediately
  • • 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

Attempt 1ImmediateTotal elapsed: 0 seconds
Attempt 230 secondsTotal elapsed: 30 seconds
Attempt 32 minutesTotal elapsed: 2 min 30 sec
Attempt 410 minutesTotal elapsed: 12 min 30 sec
Attempt 530 minutesTotal elapsed: 42 min 30 sec
Attempt 61 hourTotal elapsed: 1 hr 42 min
Attempt 72 hoursTotal elapsed: 3 hr 42 min
Attempt 84 hoursTotal elapsed: 7 hr 42 min
Attempt 98 hoursTotal elapsed: 15 hr 42 min
Attempt 1012 hoursTotal elapsed: 27 hr 42 min

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?

TimeoutWill Retry

No response within 30 seconds

5xxWill Retry

Server error responses

Connection ErrorWill Retry

DNS failure, connection refused, TLS errors

4xx (except 410)No Retry

Client errors

410 GoneNo Retry

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

1

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.

2

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.

3

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

typescript
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:

Day 4Email notification sent to account admins warning of impending disable
ImmediateDashboard shows "Degraded" status with failure metrics
On Disablewebhook.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.

POST/webhooks/{webhook_id}/test

Request

json
{
  "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.

Sandbox: 60 req/min
Production: 1,000 req/min