Skip to main content

Overview

DNSRadar sends webhook events to notify your application in real-time when DNS monitoring events occur. When a monitor detects a change in DNS records, we’ll send an HTTP POST request to your configured webhook endpoint with details about the event.
Webhooks enable you to build automated workflows and integrations that respond immediately to DNS changes without polling the API.

Event Payload Structure

When an event occurs, we send a POST request to your webhook URL with the following JSON payload:
{
  "id": "req_abc123...",
  "webhook_id": "whk_abc123...",
  "event": {
    "id": "evt_abc123...",
    "monitor_id": "mon_abc123...",
    "occurred": "2023-11-07T05:31:56Z",
    "previous_value": [
      "192.168.1.1"
    ],
    "current_value": [
      "10.0.0.1"
    ],
    "old_state": "MISMATCH",
    "new_state": "VALID",
    "incidence_count": 0,
    "domain": {
      "domain": "example.com",
      "subdomain": "www",
      "record_type": "A",
      "expected_value": [
        "192.168.1.1"
      ],
      "is_exact_match": false
    }
  },
  "created": "2023-11-07T05:31:56"
}

Payload Fields

id
string
required
Unique identifier for this webhook request
webhook_id
string
required
Unique identifier of the webhook configuration
event
object
required
The monitoring event that triggered the webhook
event.id
string
required
Unique identifier for the event
event.monitor_id
string
required
Unique identifier of the monitor that detected the change
event.occurred
string
required
ISO 8601 timestamp when the event occurred
event.previous_value
array
The DNS record values before this event (if available)
event.current_value
array
The current DNS record values (if available)
event.old_state
string
required
The previous state of the monitor (VALID, MISMATCH, ERROR)
event.new_state
string
required
The current state of the monitor (VALID, MISMATCH, ERROR)
event.incidence_count
integer
required
Number of consecutive times the monitor has been in this state
event.domain
object
required
Domain configuration object for the monitor
event.domain.domain
string
required
The root domain being monitored
event.domain.subdomain
string
The subdomain being monitored (if applicable)
event.domain.record_type
string
required
The DNS record type (A, AAAA, CNAME, MX, TXT, etc.)
event.domain.expected_value
array
required
The expected DNS record values for the monitor
event.domain.is_exact_match
boolean
required
Whether the monitor requires an exact match of DNS values
created
string
required
ISO 8601 timestamp when the webhook request was created

Retry Logic

DNSRadar implements an automatic retry mechanism to ensure reliable delivery of webhook events. If your endpoint returns a non-2xx status code, we’ll retry the request using an incremental backoff strategy.

Retry Schedule

AttemptDelay
1st retry5 minutes
2nd retry10 minutes
3rd retry15 minutes
4th retry30 minutes
5th retry1 hour
6th retry2 hours
7th retry6 hours
8th retry12 hours
9th retry24 hours
After the 9th retry fails, we’ll stop attempting to deliver the event and notify you. All events for this webhook will be paused until a successful request is made.

Webhook Pausing

If a webhook consistently fails, all events attached to that webhook will be automatically paused to prevent further failed attempts. The webhook will resume automatically once a request succeeds.
Monitor your webhook health in the DNSRadar Dashboard to identify and resolve issues before events are paused.

Security

Webhook Signatures

Every webhook request includes cryptographic signatures to verify authenticity and prevent unauthorized requests.

Signature Headers

X-DNSRadar-Signature
string
required
HMAC-SHA256 signature of the raw request body, signed with your webhook secret
X-Webhook-Timestamp
integer
required
Unix timestamp (in seconds) when the webhook was sent

Verifying Signatures

To ensure the webhook request is legitimate and hasn’t been tampered with, verify the signature on every request:
import crypto from 'crypto';

function verifyWebhookSignature(payload, signature, secret, timestamp) {
  // Check timestamp to prevent replay attacks (within 5 minutes)
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - timestamp) > 300) {
    throw new Error('Webhook timestamp is too old');
  }

  // Compute HMAC signature
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(payload);
  const computedSignature = hmac.digest('hex');

  // Compare signatures securely
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computedSignature))) {
    throw new Error('Invalid webhook signature');
  }

  return true;
}

// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-dnsradar-signature'];
  const timestamp = parseInt(req.headers['x-webhook-timestamp']);
  const secret = process.env.WEBHOOK_SECRET;

  try {
    verifyWebhookSignature(req.body, signature, secret, timestamp);

    // Parse and process the event
    const event = JSON.parse(req.body);
    console.log('Received event:', event.event.id);

    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook verification failed:', error.message);
    res.status(401).send('Unauthorized');
  }
});
Always verify the timestamp to prevent replay attacks. We recommend rejecting webhooks with timestamps older than 5 minutes.

Best Practices

Your webhook endpoint should respond with a 2xx status code within 30 seconds. Process events asynchronously if needed.
app.post('/webhook', async (req, res) => {
  // Verify signature first
  verifyWebhookSignature(req.body, ...);

  // Respond immediately
  res.status(200).send('OK');

  // Process asynchronously
  processWebhookAsync(req.body).catch(console.error);
});
Due to retries, you may receive the same event multiple times. Use the id field to deduplicate events.
const processedEvents = new Set();

function handleWebhook(event) {
  if (processedEvents.has(event.id)) {
    console.log('Duplicate event, skipping');
    return;
  }

  processedEvents.add(event.id);
  // Process the event
}
Always use HTTPS URLs for your webhook endpoints to ensure data is encrypted in transit.
Track failed webhook attempts in the DNSRadar Dashboard and set up alerts for repeated failures.
Handle errors gracefully and log failures for debugging.
@app.route('/webhook', methods=['POST'])
def webhook():
    try:
        # Verify and process webhook
        verify_webhook_signature(...)
        process_event(request.json)
        return 'OK', 200
    except Exception as e:
        # Log error but still return 200 if event was received
        logger.error(f"Error processing webhook: {e}")
        return 'OK', 200  # Prevent retries for processing errors
Use the webhook test endpoint to verify your integration before going live.
curl -X POST https://api.dnsradar.dev/webhooks/{id}/test \
  -H "X-Api-Key: your_api_key"

Example: Complete Webhook Handler

Here’s a complete example of a webhook handler with all best practices:
import express from 'express';
import crypto from 'crypto';

const app = express();
const processedEvents = new Set();

// Use raw body parser for signature verification
app.post('/webhook',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    try {
      // Extract headers
      const signature = req.headers['x-dnsradar-signature'];
      const timestamp = parseInt(req.headers['x-webhook-timestamp']);
      const secret = process.env.WEBHOOK_SECRET;

      // Verify timestamp (within 5 minutes)
      const currentTime = Math.floor(Date.now() / 1000);
      if (Math.abs(currentTime - timestamp) > 300) {
        return res.status(401).send('Timestamp too old');
      }

      // Verify signature
      const hmac = crypto.createHmac('sha256', secret);
      hmac.update(req.body);
      const computedSignature = hmac.digest('hex');

      if (!crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(computedSignature)
      )) {
        return res.status(401).send('Invalid signature');
      }

      // Parse event
      const webhook = JSON.parse(req.body);

      // Check for duplicates
      if (processedEvents.has(webhook.id)) {
        console.log(`Duplicate event: ${webhook.id}`);
        return res.status(200).send('OK');
      }

      // Mark as processed
      processedEvents.add(webhook.id);

      // Respond immediately
      res.status(200).send('OK');

      // Process asynchronously
      processEvent(webhook).catch(err => {
        console.error('Error processing event:', err);
      });

    } catch (error) {
      console.error('Webhook handler error:', error);
      res.status(500).send('Internal error');
    }
  }
);

async function processEvent(webhook) {
  const event = webhook.event;

  console.log(`Processing event ${event.id} for ${event.domain}`);
  console.log(`State changed: ${event.old_state}${event.new_state}`);

  // Your custom logic here
  if (event.new_state === 'MISMATCH') {
    await sendAlert(event);
  }
}

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});
Need help implementing webhooks? Check out our API Reference or contact support.