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
- Go to Settings > API Keys
- Click Create API Key
- Enter a descriptive name for the key (e.g., "Monitoring Dashboard", "CI Pipeline")
- 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_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6The 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/domainsAvailable Endpoints
Domains
List domains
curl -H "X-API-Key: dex_your_key_here" \
https://api.dmarc-examiner.com/api/v1/domainsResponse:
{
"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/domainsReturns 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}/verifyReturns 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-dmarcReturns 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}/statisticsReturns 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/csvAlerts
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}/dismissForensic 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/summaryGet aggregated statistics
curl -H "X-API-Key: dex_your_key_here" \
https://api.dmarc-examiner.com/api/v1/forensic-reports/statisticsWebhooks (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/webhooksEach 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=" + signatureHow to verify
- Reject requests where
|now - X-Webhook-Timestamp| > 5 minutes. - Recompute the expected signature with your stored secret and the raw body.
- Compare with constant-time equality (never
==). - 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/webhooksThe 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}/testReturns 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:
- Go to Settings > API Keys
- Click the delete icon next to the key you want to revoke
- 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