New guide: how to warm up a dedicated IP without hurting your reputation. Read now →
Log in
MigoSMTP
TroubleshootingUpdated 26 Oct 2026

DKIM "Body Hash Did Not Verify": Causes and How to Fix It

Why DKIM fails with body hash did not verify, what modifies mail after signing, how canonicalization and line endings matter, how to diagnose it from the headers and fixes for lists, gateways and templates.

IR
Written by Ishita Rao
Email Authentication Engineer
Reviewed by Mateus Oliveira
  • Published21 Apr 2026
  • Read time8 min
  • Length1,566 words
Share this guide

You published the DKIM record, the key is correct, and yet a receiving server reports dkim=fail (body hash did not verify). That wording is precise. It means the DKIM signature was found, the public key was found and the signature itself is valid, but the hash of the message body that the receiver calculated is different from the body hash the sender recorded when it signed. In plain terms, the message was changed after it was signed. This guide explains how the body hash works, lists the things that modify mail in transit, shows how to find the exact point where the change happens and gives fixes for each case. Because DKIM often carries DMARC for providers whose bounce domains do not align, a body hash failure can turn into a rejected or quarantined message, so it is worth fixing properly. For the basics of DKIM records first, see DKIM records: what they look like and how to test them.

bh=the signer's hash of the canonicalized body, stored in the signature
Mismatchthe receiver's own hash is different, so the body changed
Fixstop the edit or sign after it

How the DKIM body hash works

A DKIM signature is a header called DKIM-Signature. It contains tags such as d= (the signing domain), s= (the selector), h= (the list of signed headers), bh= (the body hash) and b= (the signature over the headers and the body hash). To create bh=, the signer first normalizes the body using a method called canonicalization, then hashes it with SHA-256 and encodes the result. The receiver repeats the same steps on the body it received and compares. If the two hashes differ, it reports a body hash failure. If the hashes match but the signature check fails, you get a different message such as "signature did not verify", which usually points at changed headers or a key problem instead.

RFC 6376 defines two canonicalization methods for the body. Simple tolerates almost no change: even trailing whitespace or an extra blank line at the end alters the hash. Relaxed ignores differences in whitespace and line-wrapping, so it survives small, harmless changes. The setting appears in the signature as c=relaxed/relaxed (header/body). Using relaxed for both is the common recommendation, because it makes signatures far more robust in transit. If you see c=simple/simple or just c=simple, a signing setting change may be the whole fix.

What changes a message after it is signed

The signature should be the last thing applied to a message. Any system that touches the body later breaks it. These are the usual causes, roughly in order of how often we see them.

CauseWhat it does to the bodyFix
Mailing list softwareAdds a footer, a subject prefix or unsubscribe textNot under your control. Rely on SPF and ARC for list traffic, or ask recipients to avoid lists for critical mail
Outbound gateway or disclaimer serviceAppends a legal disclaimer or banner after your mail server signsMove signing to the last hop, or add the disclaimer before signing
Link rewriting and click trackingReplaces URLs in the HTML after the signature was calculatedEnable tracking before the signing step, or turn the rewriting off for mail that you do not track
Security scanners at the senderRewrites links or adds markers to outgoing mailExclude your own outbound stream from rewriting, or sign after the scanner
Line ending or encoding changesConverts LF to CRLF, re-wraps long lines or changes the transfer encodingUse relaxed canonicalization and keep one component responsible for encoding
8-bit to quoted-printable conversionA relay downgrades 8-bit content to 7-bitSend with a transfer encoding that is safe end to end, such as quoted-printable or base64
Template or personalization after signingThe application fills in variables after the signer ranRender the final content first, then sign
Truncated body with the l= tagThe signature covers only a set length of the body and extra content was addedDo not use l=. It makes signatures easy to abuse and many receivers ignore it
Forwarding servicesAlter the body, add a footer or change the encodingRely on ARC for forwarded mail; see our forwarding guide

Diagnose it: find where the change happens

The goal is to compare the message at the moment it was signed with the message as it arrived. You do not need special tools to start.

  1. Send to a mailbox you control at two or three different providers and open the original message. Note whether the failure appears everywhere or only at some. A failure that appears only at a recipient that scans or rewrites mail points to that recipient's gateway, not to your system.
  2. Check the canonicalization in the DKIM-Signature header. If you see c=simple/simple, switch to relaxed/relaxed and test again.
  3. Read the Received headers from bottom to top to list every hop. If a hop belongs to your own infrastructure after the signing point, such as a disclaimer server or a security gateway, it is a candidate.
  4. Compare the body as sent and as received. Most sending systems let you save the raw message. Use a diff tool to compare it with the received copy, looking at the end of the message, at links and at whitespace.
  5. Send a minimal message. Send a plain text one-line message through the same path. If it passes, something in your HTML, links or attachments is being changed. Add parts back one at a time until it fails.

Our guide to reading email headers reads the Authentication-Results and DKIM-Signature headers for you, and the guide reading email headers to debug delivery shows what each line means.

Recompute the hash yourself

For a stubborn case, you can reproduce the receiver's calculation. Tools such as dkimpy let you verify a saved message and print the result. The sketch below checks a message file using Python.

# pip install dkimpy
import dkim

with open("received.eml", "rb") as f:
    raw = f.read()

d = dkim.DKIM(raw)
ok = d.verify(logger=__import__("logging").getLogger("dkim"))
print("verified:", ok)

Run it once on the message as it left your system and once on the message as it arrived. If the first passes and the second fails, the change happens in transit. If both fail, the change happens inside your own pipeline before or during signing.

Fixes in detail

Switch to relaxed canonicalization

In most mail servers and sending platforms, the canonicalization setting is in the signing configuration. Choose relaxed for both headers and body. This single setting removes a whole class of whitespace and wrapping failures. It does not protect against content changes such as an added footer, but it makes the signature far more tolerant of everyday handling.

Move the signing step to the end

If your mail passes through several components, such as an application, a mail server, a gateway that adds a disclaimer and a tracking layer, sign in the last component that edits the content. A common mistake is signing in the application, then letting a gateway add a footer. Either move the disclaimer into the application template or move the signer to the gateway.

If your provider rewrites links for click tracking, it must sign after it rewrites. A good provider does this automatically, but a setup that signs in your own server and then sends through a relay that adds tracking breaks the signature. Choose one place for signing and one place for tracking, and make sure the order is rewrite first, sign last. Our guide to tracking domains and alignment explains the pieces.

Fix encoding problems

Some body hash failures are caused by encoding rather than by edits. A message that contains 8-bit characters and passes through a server that does not support the 8BITMIME extension is converted, and the hash changes. To avoid the risk, encode non-ASCII content as quoted-printable or base64 before signing so that the transfer encoding is already safe. Keep the number of components that re-encode a message to one, and avoid very long lines, because some relays wrap them. See MIME, multipart and content types for how bodies are structured.

Accept that some failures belong to the recipient side

Mailing lists and forwarders change messages by design. A body hash failure at a list is not a defect in your setup. That is why DMARC accepts either SPF or DKIM to pass, and why ARC exists so that intermediaries can vouch for the original result. Your job is to make sure that mail sent directly to a mailbox verifies cleanly, and to keep SPF aligned as a second line of defence.

Confirm the fix

  1. Send a test to Gmail and to at least one other provider.
  2. Open the original message and confirm dkim=pass with header.d matching your From domain.
  3. Check that bh= in the signature is stable when you send the same message twice without personalization.
  4. Review your DMARC aggregate reports over the next few days. Failures that remain should come from lists and forwarders, not from your own streams.

If a message still fails at one recipient only, ask that recipient's administrator whether a gateway edits inbound mail, and share the raw header so they can see the mismatch. Our guide on DMARC aggregate reports shows how to see which sources fail and how often.

Check your sending domain Test the SPF, DKIM and DMARC records for your domain and see which ones pass.

Open the checker

References and further reading

Frequently asked questions

What does "body hash did not verify" mean?

The receiver calculated a different hash of the message body than the one recorded in the DKIM signature. The body was changed after it was signed, so the signature no longer matches.

Is the DKIM DNS record wrong if I see this?

Usually not. The receiver found the key and the signature is structurally valid. The problem is that the content changed after signing.

Should I use relaxed or simple canonicalization?

Use relaxed for both header and body. It tolerates whitespace and line-wrapping changes that often happen in transit, so signatures survive more handling.

Why does it fail only for mail sent through a mailing list?

Lists often add footers or subject prefixes, which changes the body. That is expected, and DMARC still passes if SPF passes and aligns.

Can link tracking cause this?

Yes. If links are rewritten after signing, the body hash no longer matches. The tracking step must run before the signing step.

Topics#dkim#body hash#authentication#troubleshooting#canonicalization
Was this guide helpful?Your answer helps us improve our documentation.

Start sending in minutes.

Create an account, add your domain and send your first email.

Get started Talk to sales