DMARC Engine
Home/Documentation/Webhooks and integrations
Documentation

Webhooks and integrations

DMARC Engine watches your domains around the clock: it ingests your DMARC aggregate reports, notices when a brand new sending source appears, spots when your published policy changes, and tracks when your SPF.

21 June 2026 · 15 min read

DMARC Engine watches your domains around the clock: it ingests your DMARC aggregate reports, notices when a brand new sending source appears, spots when your published policy changes, and tracks when your SPF record drifts towards the ten-lookup limit. All of that is useful in the dashboard, but most teams do not live in a dashboard. They live in Slack, in an on-call tool like PagerDuty, in a ticketing system, or in their own internal services. Webhooks and notification channels are how you get DMARC Engine's events out of the platform and into the tools your team already uses, so a meaningful change in your email authentication posture turns into a message a human (or another machine) actually sees within seconds.

This page explains the events DMARC Engine can emit, the notification channels you can route them to, the exact shape of the webhook payload, how delivery works (including retries and the guarantees you can and cannot rely on), how to verify that an incoming request really came from us, and worked examples for wiring up Slack and a custom HTTP endpoint. It is written against how the platform behaves today, not an idealised roadmap, so where a feature has a sharp edge we say so plainly.

The mental model: events, rules, channels

Three concepts do all the work, and it helps to keep them straight before you configure anything.

  • Events are things that happen to a domain. DMARC Engine's monitoring pipeline produces a small, fixed vocabulary of event types as it processes your reports and watches your DNS. You do not create events; the platform detects them.
  • Alert rules decide which events matter to you. A rule matches one event type (optionally scoped to specific domains), applies a threshold where one makes sense, and points at one or more channels. Rules are configured in the dashboard under Settings, and described in detail in setting up alerts.
  • Channels are destinations. A channel is one of four types: an email address, a Slack incoming webhook, a generic HTTP webhook, or a PagerDuty integration. When a rule fires, every channel it references receives the alert.

The flow is always the same. An event is detected, the alert-evaluation step checks it against your enabled rules, and any rule that matches enqueues a delivery to each of its channels. A separate dispatch worker then sends the message out to Slack, your webhook, PagerDuty or email. Decoupling detection from delivery is deliberate: it means a slow or failing endpoint on your side never blocks the monitoring pipeline, and it lets us retry delivery without re-running detection.

The events you can receive

DMARC Engine's monitoring produces these event types. Each one maps to a condition you can build an alert rule around.

  • new_source A sending IP or service that has not previously appeared for this domain shows up in your aggregate reports. This is the event most people care about most, because a new source is either something legitimate you forgot to authenticate (a new marketing tool, a new mail server) or something you did not authorise at all. Catching it early is the whole point of running DMARC at p=none before you enforce.
  • policy_change The DMARC policy published in DNS for the domain changes, for example from p=none to p=quarantine, or from quarantine to reject. This fires whether the change was made by you, by DMARC Engine's managed enforcement, or by someone else with access to the zone. It is your tripwire for unexpected edits.
  • dmarc_failure The share of mail failing DMARC for a domain crosses a threshold you set. A rule for this event takes a maxRate threshold (a percentage), so you can say "alert me when more than 10% of mail is failing" rather than being paged for every stray forwarded message.
  • spf_limit The domain's SPF record is at or near the hard limit of ten DNS lookups defined by RFC 7208. The rule takes a maxLookups threshold. Crossing ten lookups causes a permerror and effectively breaks SPF, so this is an early warning to flatten or consolidate. If DMARC Engine manages your SPF, see how SPF flattening works; to inspect a record yourself, use the SPF checker.
  • volume_spike Mail volume for a domain jumps well above its recent baseline. The rule takes a spikeRatio threshold (for example, fire when current volume is more than three times the previous period). A spike can be a legitimate campaign or a sign that someone is sending as you.

Severity is assigned automatically from the event type. A dmarc_failure is treated as critical, new_source, spf_limit and volume_spike as warning, and policy_change as info. That severity travels with the notification, so your downstream tooling can colour, route or escalate on it.

A practical note on noise: the evaluation step applies a per-domain, per-event-type cooldown of one hour, and de-duplicates so the same alert for the same domain and rule fires at most once per hour. You will not get a thousand pages because a thousand reports arrived in a batch. Build your rules knowing that backstop exists.

Notification channels

You configure channels in the dashboard under Settings, and you must be an organisation admin to create, edit, enable, disable or delete one. There are four types.

Email

The simplest channel. You give it an email address; matching alerts arrive as a branded HTML email with a plain-text fallback, a severity badge, and a link straight to the relevant view in the dashboard. Alert emails carry RFC 8058 one-click unsubscribe headers, which is what keeps recurring alert mail compliant with the Gmail and Yahoo bulk-sender rules. If you only need a human to read alerts, start here.

Slack

You provide a Slack incoming webhook URL (the https://hooks.slack.com/services/... address Slack gives you when you add an Incoming Webhooks integration to a workspace). DMARC Engine posts a formatted message to that webhook with the alert title, the severity in capitals, and the description. This is the fastest way to get DMARC events in front of a team channel. Setup is covered below.

Webhook

A generic HTTP endpoint you own. DMARC Engine sends an HTTP POST with a JSON body to the URL you configure. This is the integration to reach for when you want events in something that is not Slack or PagerDuty: a ticketing system, an internal service, a workflow automation platform, a serverless function that fans the event out further. The payload shape is documented in full in the next section.

PagerDuty

You provide a PagerDuty Events API v2 integration (routing) key. Matching alerts are sent to PagerDuty's events.pagerduty.com/v2/enqueue endpoint as a trigger event, with the alert title as the summary and the description in the custom details. Severity is mapped to PagerDuty's scale: critical events become PagerDuty critical, high becomes error, and everything else becomes warning. Use this when DMARC failures should wake someone up rather than sit in a channel.

A hard constraint on destinations

Both Slack and generic webhook channels must point at a public HTTPS URL. This is enforced twice: once when you create the channel, and again at delivery time. The delivery path resolves the hostname and refuses any address that resolves to a private, loopback, link-local or otherwise reserved range. This is an anti-SSRF measure, and it is not negotiable. Plain http://, https://localhost, https://10.0.0.5, internal hostnames that resolve to RFC 1918 space, and cloud metadata endpoints will all be rejected. If you need to receive events on an internal system, terminate them at a public HTTPS endpoint you control (an API gateway, a small public webhook receiver, a tunnelling service) and forward inward from there.

The webhook payload

When a rule fires and a webhook channel is the destination, DMARC Engine sends:

POST <your configured URL>
Content-Type: application/json

The body is a flat JSON object:

{
  "alert": "New sending source detected",
  "description": "new_source detected for domain",
  "severity": "warning",
  "timestamp": "2026-06-23T09:41:02.118Z"
}

The fields are:

  • alert A short human-readable title. This comes from the name of the alert rule that matched, so you control it: name your rules clearly ("Prod domains: new source", "billing.example.com policy changed") and that text is what lands in alert.
  • description A short description of what was detected, including the underlying event type token (new_source, policy_change, dmarc_failure, spf_limit, volume_spike). This is the field to parse if you want to branch your handling by event type.
  • severity One of critical, warning or info. Route and escalate on this.
  • timestamp An ISO 8601 UTC timestamp of when the notification was dispatched.

Keep your handler tolerant. Treat the body as JSON, read the fields you need, and ignore any you do not recognise, because the payload may gain fields over time and a handler that rejects unknown keys will break the first time that happens. Do not depend on field ordering. Always parse severity as a known small set and have a sensible default for an unexpected value rather than crashing.

Your endpoint should respond quickly with a 2xx status. Any non-2xx response is treated as a failed delivery and will be retried (see below), so do the minimum synchronously: validate, acknowledge with a 200, and do any slow work (creating a ticket, calling another API) asynchronously after you have responded. An endpoint that does heavy work inline and times out will both be slow for us and rack up duplicate retries for you.

Delivery semantics: what you can rely on

Understanding the guarantees matters, because it determines how you write your handler.

  • Delivery is at-least-once, not exactly-once. Notifications are queued and delivered by a worker backed by Cloudflare Queues. If a delivery succeeds on our side but the acknowledgement is lost, or your endpoint returns a 2xx but the network drops the response, the same message can be delivered again. Design your handler to be idempotent. The simplest approach: derive a stable key from the payload (for example, a hash of alert plus description plus timestamp) and skip processing if you have seen it before. DMARC Engine itself de-duplicates on its own internal message id so it will not knowingly send you the same alert twice, but you should still defend against redelivery on your side.
  • Failures are retried with backoff. A timeout, a 5xx, a connection refusal or any non-2xx response is a transient failure. The dispatcher retries with exponential backoff (roughly doubling, capped at ten minutes between attempts). A persistently failing endpoint eventually exhausts its retries and the message is routed to a dead-letter queue, where it is logged loudly and not reprocessed. In other words: brief outages on your side are forgiven; a permanently broken endpoint will silently stop receiving after the retries run out, so monitor your own receiver.
  • Some failures are permanent and not retried. If your URL fails the public-HTTPS or SSRF check at delivery time (for example, DNS now resolves it to a private address), or the channel config is malformed, that delivery is marked failed and not retried, because retrying cannot fix it. Fix the channel configuration and the next event will deliver.
  • Disabled channels are skipped silently. If a channel is disabled, queued deliveries to it are dropped, not held. Re-enable the channel before you expect alerts. This is also why the dashboard refuses to send a test through a disabled channel: it would look like success while delivering nothing.
  • There is a timeout. Each outbound delivery is time-bounded (about eight seconds). If your endpoint has not responded in that window the attempt is treated as failed and retried. Respond fast.

Verifying that a request really came from DMARC Engine

This is the part to read carefully, because honesty matters more than marketing here.

The generic webhook POST that DMARC Engine sends today carries the JSON body and a Content-Type header. It does not currently include a per-request HMAC signature header. So you should not write a handler that rejects requests lacking an X-Signature-style header, because no such header is sent. Verification today relies on the mechanisms that genuinely exist:

  1. Use a hard-to-guess URL. Your webhook endpoint URL is itself a secret. Include a long random token as a path segment or query parameter, for example https://hooks.example.com/dmarc/9f3c1a2b8d4e.... Reject any request to the endpoint that does not present the exact token. Because all delivery is over HTTPS, that token is never sent in cleartext on the wire. Treat the URL as you would an API key: store it in your secret manager, never log it, and rotate it (delete the channel and create a new one with a fresh token) if it leaks.
  2. Constrain to HTTPS and known behaviour. Only accept POST, only accept application/json, and only accept the payload shape documented above. Reject anything else. This will not stop a determined attacker who has your secret URL, but it stops casual probing and malformed traffic.
  3. Validate the content, not just the transport. A real DMARC Engine alert references one of the known event tokens in description and a known severity. If those do not match, drop the request.

If you require cryptographic authentication of the payload itself, the robust pattern that works with the current platform is a shared secret you embed and echo: put a secret in the webhook URL (as above), and in your handler compute your own HMAC over the received body using that same secret, comparing it to a value you store. That gives you tamper-evidence on the body without depending on a header the platform does not yet send. If first-class per-request signing is on your requirements list, raise it with support so it can be prioritised; we would rather you build on what is real than on a header that is not there.

For Slack and PagerDuty, verification is handled by the destination's own model: the Slack incoming webhook URL and the PagerDuty routing key are themselves the credential, so protect them the same way you protect any secret.

Connecting Slack: step by step

  1. In Slack, create an Incoming Webhook for the channel you want alerts to land in. The Slack admin guide walks through adding the Incoming Webhooks app and choosing a channel; the result is a URL of the form https://hooks.slack.com/services/T000/B000/XXXX.
  2. Copy that URL. It is a secret. Anyone with it can post to your channel, so do not paste it into a ticket or a public chat.
  3. In the DMARC Engine dashboard, open Settings, go to notification channels, and add a new channel of type Slack. Give it a clear name (the name is for your reference, not Slack), paste the webhook URL, and save. The platform validates that the URL is a public HTTPS address before accepting it.
  4. Send a test from the channel's controls. A test message is delivered through the real pipeline, so if it appears in Slack, your configuration is correct end to end. If the channel is disabled, enable it first; tests against a disabled channel are refused on purpose.
  5. Create or edit an alert rule to point at this Slack channel. Decide which events matter (most teams start with new_source and policy_change), scope the rule to the right domains, set thresholds where relevant, and save.

The message Slack receives is formatted as the bold alert title, the severity in capitals in brackets, then the description, so it reads cleanly in a channel without any extra Slack app configuration on your side.

Connecting a custom webhook: step by step

  1. Stand up a public HTTPS endpoint that accepts POST application/json. Make the path unguessable by including a long random token, and have the handler reject any request that does not present that token.
  2. In Settings, add a notification channel of type Webhook, name it, and paste the full HTTPS URL including the token. The platform validates it as a public HTTPS address.
  3. Send a test and confirm your endpoint received it and returned a 2xx. Check your own logs to see the exact payload arrive.
  4. Make the handler idempotent and fast: acknowledge with 200 immediately, de-duplicate on a key derived from the payload, and do slow downstream work asynchronously.
  5. Attach the channel to one or more alert rules.

A minimal receiver, in Node-style pseudocode, looks like this:

// POST /dmarc/<secret-token>
export async function handler(req, res) {
  if (req.params.token !== process.env.DMARC_WEBHOOK_TOKEN) {
    return res.status(404).end();            // wrong/missing secret
  }
  const { alert, description, severity, timestamp } = req.body;

  // idempotency: skip if we have already handled this exact event
  const key = hash(`${alert}|${description}|${timestamp}`);
  if (await seen(key)) return res.status(200).end();
  await remember(key);

  res.status(200).end();                      // acknowledge fast

  // do slow work after responding
  await routeBySeverity(severity, alert, description);
}

From that endpoint you can fan out anywhere: open a ticket, post to a different chat tool, call an internal service, or write to a queue of your own. The custom webhook is the universal adapter; everything not natively supported hangs off it.

How webhooks relate to the API

Webhooks and the DMARC Engine API solve complementary problems. Webhooks are push: the platform tells you when something changed, so you do not poll. The API is pull and control: you ask for current state or make changes (add a domain, read a record, fetch report data) on demand. A common, robust pattern is to use a webhook as the trigger and the API as the source of truth: when a new_source or policy_change arrives, your handler calls the API to fetch the full current detail for that domain rather than relying on the summary text in the payload. That keeps your integration correct even as the payload evolves, because the API is where the structured, authoritative data lives. See automating DMARC with the API for authentication, endpoints and rate-limiting.

Practical recommendations

  • Start narrow. Turn on new_source and policy_change first. They are the highest-signal events while you are still moving a domain through the enforcement journey, and they rarely fire once your sources are all authenticated.
  • Use one channel per destination, named clearly. A separate channel for Slack, for PagerDuty, for your webhook receiver. When you need to silence one path, disable that channel and leave the others running.
  • Route on severity. Send critical (DMARC failures) to PagerDuty, warning and info to Slack or email. Your on-call rota should not be paged for a policy note.
  • Scope rules to domains. A volume_spike threshold that is right for a high-volume marketing domain is wrong for a quiet transactional one. Per-domain rules keep both useful.
  • Monitor your own receiver. Because a permanently failing webhook eventually stops being retried, your endpoint going dark is itself a failure mode. Alert on the absence of expected test traffic, or send a periodic test from the dashboard.
  • Keep destination URLs secret and rotatable. Slack URLs, PagerDuty keys and your webhook token are all bearer credentials. Store them in a secret manager, never log them, and rotate by replacing the channel if one leaks.

Where to go next

Webhooks turn DMARC Engine from a place you visit into a system that tells you when your email authentication posture moves. Wire new_source and policy_change into the channel your team actually reads, make your handler idempotent and fast, keep your destination URLs secret, and you will hear about the changes that matter long before they become an incident.

Share

See where your domain stands today

Run a free DMARC scan, then let us take you to enforced p=reject with no email outage.