Documentation

Learn how to integrate and use DDMARC.

Outbound

Outbound Webhooks

When DDMARC raises an alert, it can POST that alert to a URL you own. This page describes exactly what arrives, and is equally explicit about the delivery guarantees that do not exist.

Not available yet — signature verification, retries and event subscriptions

Outbound webhook delivery works today, but it is deliberately minimal. There is no request signing, no retry policy, and no per-event subscription model. If you write a receiver that verifies an HMAC signature before processing, that check will never pass, because no signature header is ever sent — your handler would silently drop every alert while appearing correct in code review. Authenticate deliveries another way: keep the URL secret, put a random token in the path, or terminate it behind mutual TLS or an allowlist.

How Delivery Works

There is no webhook resource to create. Each user has a single webhook URL on their notification preferences, and DDMARC fans an alert out to every enabled URL in the organization.

1

Save a URL

In the dashboard, go to Notifications → Settings → Webhook, or send PATCH /api/v1/alerts/preferences with webhook_url and webhook_enabled: true. The URL must be HTTPS.

2

Choose which alerts fire

The per-alert-type toggles on the same preferences object decide what raises an alert at all. For anything conditional, create an alert rule and list webhook in its notification_channels.

3

Receive the POST

DDMARC sends one JSON POST per alert, per enabled URL, with a 10 second timeout. Any 2xx (200, 201, 202 or 204) counts as delivered. Reply fast and do your work asynchronously.

Payload

A generic endpoint receives four top-level fields. Everything about the alert itself is nested under alert.

POST to your endpointapplication/json
{
  "event_type": "alert.dns_record_changed",
  "timestamp": "2026-07-19T04:31:07.918204+00:00Z",
  "organization_public_id": "org_7f2c91ab",
  "alert": {
    "alert_id": 88213,
    "alert_type": "dns_record_changed",
    "title": "DMARC record changed on example.com",
    "message": "The published policy moved from p=quarantine to p=none.",
    "severity": "warning",
    "domain": "example.com",
    "domain_id": 42,
    "organization_id": 17
  }
}
FieldTypeDescription
event_typestringAlways alert.<alert_type>, for example alert.high_failure_rate
timestampstringWhen the delivery was built, ISO 8601
organization_public_idstringYour organization's opaque public id
alertobjectThe alert body: alert_id, alert_type, title, message, severity, domain, domain_id, organization_id

severity is one of info, warning or critical. Alerts raised directly from report ingestion may carry a few extra keys inside alert, so treat it as an open object and read only the fields you need.

Slack and Discord Get a Different Body

The payload above is what a generic endpoint receives. DDMARC inspects the URL you saved and, if it recognises a chat platform, sends that platform's native format instead of the JSON envelope.

URL containsBody sent
hooks.slack.comSlack Block Kit: a header block with a severity emoji, a section block with the message, and a context block showing type and severity. No action buttons.
discord.com/api/webhooksA Discord embed with a severity-coloured stripe and Type and Severity fields.
anything elseThe generic JSON payload shown above.

Microsoft Teams, Telegram and Jira are separate channels with their own configuration fields rather than URL sniffing. See the integrations overview.

Alert Types

event_type is always the alert type prefixed with alert. These are the types that exist. Which of them you actually receive depends on your plan, your notification preferences and your alert rules.

Authentication

  • dmarc_fail
  • spf_fail
  • dkim_fail
  • high_failure_rate
  • forensic_spike

Senders and reports

  • new_sender
  • new_report_received

DNS

  • dns_issue
  • dns_record_changed
  • dns_regressed
  • dns_verified

Policy and rollouts

  • policy_published
  • rollout_started
  • rollout_stage_advanced
  • rollout_blocked
  • rollout_completed

Managed hosting

  • hosted_spf_refresh_failed
  • hosted_spf_delegation_lost
  • hosted_dkim_cname_lost
  • hosted_dkim_key_age
  • hosted_mta_sts_delegation_lost
  • hosted_mta_sts_serving_broken
  • hosted_bimi_delegation_lost

Other

  • lookalike_domain
  • rule_triggered
POST

Send a Test Delivery

Sends a synthetic alert so you can confirm your receiver works before waiting for a real one. Two variants exist: one for an ad-hoc URL, one for the URL already saved on your preferences.

POST /api/v1/alerts/webhook/test
curl -X POST "https://api.ddmarc.com/api/v1/alerts/webhook/test" \
  -H "X-API-Key: $DDMARC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.example.com/ddmarc" }'
Delivered200 OK
{
  "success": true,
  "status_code": 200,
  "error": null
}
Rejected by the SSRF guard200 OK
{
  "success": false,
  "status_code": null,
  "error": "URL resolves to a private address"
}

A failed test is still 200 — branch on success. Use POST /api/v1/alerts/webhook/test-saved with no body to test the URL already stored on your preferences; that variant returns 400 if no URL is saved or webhooks are disabled.

Delivery Guarantees

What you can rely on

  • HTTPS is required; plain HTTP URLs are rejected when you save them.
  • Outbound requests are SSRF-hardened: the hostname is resolved and validated once, private, loopback, link-local and cloud metadata ranges are refused, IPv4-mapped and 6to4 addresses are unwrapped before the check, the connection is pinned to the validated IP with SNI preserved, and redirects are not followed.
  • The request times out after 10 seconds.
  • Any 2xx status is treated as success; anything else is a failure.

Design around this

  • • A missed delivery is gone. Poll GET /api/v1/alerts if you need a complete record.
  • • Treat the webhook as a notification channel, not as a source of truth for state.
  • • Return 2xx immediately and queue the work; slow handlers will hit the 10 second timeout and be recorded as failed.
  • • Because the URL is the only credential, rotate it if it ever leaks — there is nothing else to revoke.

Not Implemented

These are commonly expected of a webhooks product and are absent from DDMARC today. They are listed explicitly so you do not build against them.

Request signing

No HMAC signature, no shared secret, no X-DDMARC-Signature header. The only header sent is Content-Type: application/json. A receiver that rejects unsigned requests will drop every DDMARC delivery.

Retries and backoff

Delivery is a single POST. A timeout, a connection error or a non-2xx response is logged and abandoned; nothing is queued or replayed.

Automatic disabling

A consistently failing endpoint is never disabled, and no failure counter is exposed. Watch your own receiver.

Per-endpoint event subscriptions

There is no way to say "send me only dns_record_changed". Which alerts fire is controlled by your notification preferences and alert rules, and every alert that fires goes to your one webhook URL.

Event ids and deduplication

The payload has no event id. The alert dictionary does carry alert_id, which is stable per alert, but nothing guarantees at-most-once delivery for you to deduplicate against.

A webhooks CRUD resource

There is no /webhooks collection to create, list, update or delete endpoints. Configuration is one URL on your notification preferences.

Continue to