API Keys

API keys let you access your DMARC Examiner data programmatically through the REST API. Use them to integrate DMARC monitoring into your own tools, dashboards, or automation workflows.

Creating an API Key

  1. Go to Settings > API Keys
  2. Click Create API Key
  3. Enter a descriptive name for the key (e.g., "Monitoring Dashboard", "CI Pipeline")
  4. Copy the key immediately — it will only be shown once

Each organization can have up to 5 active API keys. Keys are scoped to the organization and provide read access to all domains, reports, and alerts within it.

API Key Format

API keys follow the format dex_ followed by 32 hexadecimal characters:

dex_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6

The dex_ prefix identifies the key as a DMARC Examiner API key. In the Settings UI, only the first 8 characters are displayed for identification.

Authentication

Include your API key in the X-API-Key header with every request:

curl -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/domains

Available Endpoints

Domains

List domains

curl -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/domains

Response:

{
  "data": [
    {
      "id": "uuid",
      "name": "example.com",
      "status": "active",
      "verified_at": "2025-01-15T10:30:00Z",
      "last_report_at": "2025-03-20T08:00:00Z",
      "created_at": "2025-01-10T09:00:00Z"
    }
  ]
}

Create a domain

curl -X POST \
  -H "X-API-Key: dex_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name":"example.com"}' \
  https://api.dmarc-examiner.com/api/v1/domains

Returns 201 Created with the new domain. The response includes reporting_email and dmarc_record — copy those into your DNS to start receiving DMARC reports. Returns 409 Conflict if the domain already exists in the organization, or a plan-limit error if the domain count exceeds your plan.

Get a single domain

curl -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/domains/{domain_id}

Verify a domain

Re-check the DNS for your reporting email and update the domain status if found.

curl -X POST \
  -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/domains/{domain_id}/verify

Returns 200 OK with {"verified": true, "domain": {...}} on success, or 422 Unprocessable Entity with the reason if the DNS does not yet contain the reporting email.

Delete a domain

curl -X DELETE \
  -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/domains/{domain_id}

Returns 204 No Content on success.

Check existing DMARC record (any domain)

A free utility — does not require the domain to belong to your organization. Useful for due diligence before onboarding a new domain.

curl -X POST \
  -H "X-API-Key: dex_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com"}' \
  https://api.dmarc-examiner.com/api/v1/check-dmarc

Returns the existing DMARC record (if any), parsed policy and reporting addresses.

Reports

List reports

curl -H "X-API-Key: dex_your_key_here" \
  "https://api.dmarc-examiner.com/api/v1/reports?domain_id=uuid&page=1&limit=20"

Query parameters:

Parameter Type Description
domain_id string Filter by domain UUID
date_from integer Unix timestamp, filter reports starting from
date_to integer Unix timestamp, filter reports ending at
page integer Page number (default: 1)
limit integer Results per page (default: 20, max: 100)

Get a single report

curl -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/reports/{report_id}

Returns the report with all individual records, including geolocation data.

Get report statistics

curl -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/reports/{report_id}/statistics

Returns aggregated statistics grouped by country, ASN, and authentication result.

Export report as CSV

curl -H "X-API-Key: dex_your_key_here" \
  -o report.csv \
  https://api.dmarc-examiner.com/api/v1/reports/{report_id}/export/csv

Alerts

List alerts

curl -H "X-API-Key: dex_your_key_here" \
  "https://api.dmarc-examiner.com/api/v1/alerts?severity=critical&status=pending"

Query parameters:

Parameter Type Description
severity string Filter by severity: info, warning, critical
status string Filter by status: pending, sent, dismissed
page integer Page number (default: 1)
limit integer Results per page (default: 20, max: 100)

Dismiss an alert

curl -X PUT \
  -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/alerts/{alert_id}/dismiss

Forensic reports (Pro plan and above)

Forensic reports (DMARC RUF) contain the failed-message details — sender IP, original MAIL FROM, authentication results per check. Endpoints return 403 Forbidden if the current plan does not include forensic reports.

List forensic reports

curl -H "X-API-Key: dex_your_key_here" \
  "https://api.dmarc-examiner.com/api/v1/forensic-reports?domain_id=uuid&page=1&limit=20"

Query parameters: domain_id, date_from, date_to, source_ip, auth_failure (dmarc / spf / dkim), page, limit.

Get a single forensic report

curl -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/forensic-reports/{forensic_report_id}

Returns the full record including parsed authentication results, geolocation of the source IP and original headers (when present).

Get summary (last 7 days)

curl -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/forensic-reports/summary

Get aggregated statistics

curl -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/forensic-reports/statistics

Webhooks (Pro plan and above)

Webhooks let you subscribe to events and have us POST them to your URL. Today the system emits a single event:

  • alert.created — fired whenever an alert is created in your organization (from report processing or DNS change detection).

Other event types (e.g. report.received, domain.verified) are NOT emitted at the moment.

Endpoints return 403 Forbidden if the plan does not include advanced alerting. Failed deliveries are retried twice (30 s, 120 s) and the webhook auto-disables after 10 consecutive failures.

List webhook endpoints

curl -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/webhooks

Each webhook is created with a secret that you store on your side. Every delivery is signed with HMAC-SHA256 — the secret never travels on the wire.

How deliveries are signed

Each POST to your URL carries:

Header Value
Content-Type application/json
User-Agent DMARC-Examiner-Webhooks/1.0
X-Webhook-Timestamp Unix seconds when we signed the request
X-Webhook-Signature sha256=<hex> — see below

Where:

signed_value = "{X-Webhook-Timestamp}.{raw_request_body}"
signature    = hex(HMAC-SHA256(secret, signed_value))
header       = "sha256=" + signature

How to verify

  1. Reject requests where |now - X-Webhook-Timestamp| > 5 minutes.
  2. Recompute the expected signature with your stored secret and the raw body.
  3. Compare with constant-time equality (never ==).
  4. Only then parse the body.

Node.js

import crypto from 'node:crypto'

function verify(req, secret) {
  const timestamp = req.headers['x-webhook-timestamp']
  const sigHeader = req.headers['x-webhook-signature']
  const rawBody = req.rawBody // string of bytes, NOT parsed JSON

  if (!timestamp || !sigHeader?.startsWith('sha256=')) return false
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex')

  const got = sigHeader.slice('sha256='.length)
  return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(got, 'hex'))
}

Python

import hmac, hashlib, time

def verify(headers, raw_body: bytes, secret: str) -> bool:
    ts = headers.get('X-Webhook-Timestamp')
    sig = headers.get('X-Webhook-Signature', '')
    if not ts or not sig.startswith('sha256='):
        return False
    if abs(time.time() - int(ts)) > 300:
        return False
    signed = f"{ts}.{raw_body.decode()}".encode()
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig[len('sha256='):])

PHP

function verify(array $headers, string $rawBody, string $secret): bool {
    $ts = $headers['X-Webhook-Timestamp'] ?? null;
    $sig = $headers['X-Webhook-Signature'] ?? '';
    if (!$ts || !str_starts_with($sig, 'sha256=')) return false;
    if (abs(time() - (int) $ts) > 300) return false;
    $expected = hash_hmac('sha256', $ts . '.' . $rawBody, $secret);
    return hash_equals($expected, substr($sig, strlen('sha256=')));
}

Common pitfalls

  • Frameworks parse JSON before your handler runs, but the signature is over the raw bytes. Capture the raw body before parsing (Express: bodyParser.raw(), Symfony: $request->getContent(), Flask: request.get_data()).
  • Using == instead of constant-time compare is a timing-attack vulnerability.
  • Skipping the timestamp window check is a replay-attack vulnerability.

Create a webhook endpoint

curl -X POST \
  -H "X-API-Key: dex_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/dmarc-events","name":"Production"}' \
  https://api.dmarc-examiner.com/api/v1/webhooks

The URL must be HTTPS. Each organization can have a limited number of active webhooks.

Update a webhook

curl -X PUT \
  -H "X-API-Key: dex_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"is_active":false}' \
  https://api.dmarc-examiner.com/api/v1/webhooks/{webhook_id}

Accepts any combination of url, name, is_active.

Delete a webhook

curl -X DELETE \
  -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/webhooks/{webhook_id}

Send a test event

curl -X POST \
  -H "X-API-Key: dex_your_key_here" \
  https://api.dmarc-examiner.com/api/v1/webhooks/{webhook_id}/test

Returns the delivery result (HTTP status, response time, error if any).

Rate Limits

API requests are limited to 100 requests per minute per API key. When the limit is exceeded, the API returns a 429 Too Many Requests response.

Revoking a Key

To revoke an API key:

  1. Go to Settings > API Keys
  2. Click the delete icon next to the key you want to revoke
  3. Confirm the revocation

Revoked keys stop working immediately and cannot be restored. You will need to create a new key and update your integrations.

Security Best Practices

  • Never expose API keys in client-side code or public repositories
  • Use environment variables to store keys in your applications
  • Create separate keys for different integrations so you can revoke them independently
  • Rotate keys periodically by creating a new key, updating your integration, and then revoking the old one