Skip to content
Reference · outbound only

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 and event subscriptions
Outbound webhook delivery works today, but it is deliberately minimal. There is no request signing 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.
01 — Setup

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. A failed delivery is retried: three attempts in total, one second and then three seconds apart (or as long as a Retry-After header asks, up to ten seconds), after a timeout, a connection error, a 429 or a 5xx. Any other 4xx is not retried.

01 — Save a URLIn 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.
02 — Choose which alerts fireThe 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.
03 — Receive the POSTOne 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.
02 — What arrives

The payload

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

POST to your endpoint · application/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.
03 — Not the same body

Slack and Discord differ

The payload above is what a generic endpoint receives. DDMARC inspects the URL you saved and, if it recognizes 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-colored 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, not URL sniffing. See the integrations overview.

04 — The catalog

Alert types

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

Authenticationdmarc_fail · spf_fail · dkim_fail · high_failure_rate · forensic_spike
Senders and reportsnew_sender · new_report_received
DNSdns_issue · dns_record_changed · dns_regressed · dns_verified
Policy and rolloutspolicy_published · rollout_started · rollout_stage_advanced · rollout_blocked · rollout_completed
Managed hostinghosted_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
Otherlookalike_domain · rule_triggered
05 — POST /api/v1/alerts/webhook/test

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.

Request
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" }'
Delivered · 200 OK
{
  "success": true,
  "status_code": 200,
  "error": null
}
Rejected by the SSRF guard · 200 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.
06 — The contract

Delivery guarantees

What you can rely on
HTTPS is requiredPlain HTTP URLs are rejected when you save them.
Outbound requests are SSRF-hardenedThe 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; redirects are not followed.
A 10 second timeoutAfter which the delivery is recorded as failed.
Any 2xx is successAnything else — including a redirect — is a failure.
Design around this
A delivery that fails three attempts is goneNothing is queued after that. Poll GET /api/v1/alerts if you need a complete record.
It is a notification channel, not a source of truthDo not reconstruct state from webhook history you did not receive.
Return 2xx immediately and queue the workSlow handlers hit the 10 second timeout and are recorded as failed.
The URL is the only credentialRotate it if it ever leaks — there is nothing else to revoke.
07 — Absent

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 signingNo 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.
Automatic disablingA consistently failing endpoint is never disabled, and no failure counter is exposed. Watch your own receiver.
Per-endpoint event subscriptionsThere 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 deduplicationThe 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 resourceThere is no /webhooks collection to create, list, update or delete endpoints. Configuration is one URL on your notification preferences.