DKIM fail: "body hash did not verify" — what it means and how to fix it
This error has one meaning: the message body the receiver got is not the body that was signed. Something between the signer and the inbox changed the content. Here's how to find out what — and what to do about it.
What the error literally means
When a server signs a message, it computes a hash of the body and stores it in the bh= tag of the DKIM-Signature: header. The receiver recomputes that hash over the body it received. If the two don't match, verification stops with body hash did not verify — the signature's cryptography never even gets checked.
Two useful things follow from that:
- It's not a DNS problem. A missing or wrong selector record produces a different failure (key not found / signature error). Body hash failures are about the content.
- The body was modified after signing. Even one added character, an injected footer, or rewritten line endings is enough — that sensitivity is the whole point of the signature.
The usual culprits, most common first
| Cause | Typical sign | Fix |
|---|---|---|
| Mailing list adds a footer or subject tag | Fails only for list traffic | Nothing to fix on your side — see below |
| A gateway appends a disclaimer or scans/rewrites content | Fails for one recipient org only | Sign after the disclaimer step, or stop modifying outbound mail |
| Forwarding service rewrites the message | Fails only for forwarded copies | Nothing to fix — rely on receivers honoring ARC |
| Your own pipeline edits mail after signing (open-tracking injection, template post-processing, CRLF normalization) | Fails everywhere, consistently | Reorder the pipeline: signing must be the last step |
Strict canonicalization (c=simple/simple) | Intermittent fails on harmless whitespace changes | Switch to relaxed/relaxed |
How to diagnose which one you have
- Send yourself a direct test. From the failing platform to a Gmail address you control, then open Show original. If the direct copy shows
dkim=pass, your signing is fine — the failures come from something in transit (lists, forwarders, a recipient's gateway). - Check who's failing in your aggregate reports. Your DMARC reports break failures down by source. A few failures scattered across many receivers usually means forwarding; consistent failure from one sending source means that source's pipeline modifies mail after signing.
- Look at the
c=tag in a failing message'sDKIM-Signatureheader.simplecanonicalization treats trivial whitespace changes as tampering; almost every platform should be onrelaxed/relaxed.
If it's mailing lists or forwarders: stop chasing it
A list that adds "[listname]" to subjects or an unsubscribe footer will break your DKIM body hash, by design — the list changed the message. You cannot fix another organization's rewriting. This is expected background noise in DMARC data:
- A small percentage of body-hash failures on otherwise-passing mail is normal. That's one reason the rollout advice is monitor on p=none first — you learn your baseline before enforcing.
- Modern receivers use ARC to recover the original authentication result for well-behaved forwarders, so enforcement hurts less than the raw failure count suggests.
- What you're actually watching for is different: a new source failing consistently. That's either a misconfigured tool of yours — or someone spoofing you.
If it's your own sending: sign last
Consistent body-hash failures on direct mail mean your content changes after signing. The fix is always ordering: DKIM signing must be the final step that touches the message. Typical offenders are disclaimer appliances behind the signer, open-tracking pixel injection after templating, and relays that re-encode or normalize line endings. Move the signer to the edge, or move the modification before it.
Tired of decoding failure tables by hand? PlainDMARC reads your aggregate reports and tells you in plain English which failures are forwarding noise and which are a real sender you need to fix — one weekly verdict per domain.
Get a plain-English weekly DMARC verdict — create a free account