Skip to content
Engineering

Email Signed By: Verify Webhook Payload Authenticity

| | 8 min read
Email Signed By: Verify Webhook Payload Authenticity
Email Signed By: Verify Webhook Payload Authenticity

Seeing “email signed by” in Gmail (or similar UI cues in other clients) is a reminder that identity and integrity matter in email workflows. But if you are consuming inbound email through webhooks (for QA automation, signup verification, or LLM agents), the security boundary shifts: your real risk is often not whether the SMTP message had DKIM, but whether the HTTP request delivering the parsed email is authentic.

This guide explains what “email signed by” actually means, why it is not enough for webhook-driven automation, and how to verify webhook payload authenticity in a way that holds up under retries, duplicates, and adversarial input.

If you are using Mailhook specifically, it supports real-time webhook notifications and signed payloads for received emails as structured JSON. For the canonical, up-to-date integration contract (exact headers, signature construction, and examples), use: mailhook.co/llms.txt.

What “Email Signed By” really means

Most inbox UIs surface “signed by” when the message passed cryptographic authentication checks, typically DKIM (DomainKeys Identified Mail). DKIM lets a sending domain sign parts of the message so receivers can detect tampering in transit and associate the message with a domain.

Important nuance:

  • DKIM helps the receiver answer “Did this message likely originate from an authorized sender for this domain, and was it modified in transit?”
  • DKIM does not authenticate the HTTP request your system receives later from a webhook provider.

Even if a message is perfectly DKIM-signed, a malicious actor can still try to:

  • Spoof your webhook endpoint with a fake “new email received” payload
  • Replay an older valid payload to re-trigger an OTP flow
  • Tamper with JSON fields if your verification is not computed over the raw body

So treat “email signed by” as useful context for the email itself, not as a substitute for webhook authentication.

DKIM vs webhook signatures: two different trust problems

Mechanism What it authenticates Where it applies What it does not protect you from
DKIM (shown as “email signed by”) Parts of the email message signed by a domain SMTP and mailbox receipt Forged HTTP webhook calls, replayed deliveries, your own parsing/normalization pipeline
Webhook signature The HTTP request body delivered to your endpoint Your application boundary A “legitimate” email containing malicious content (prompt injection, SSRF links, etc.)

Best practice in automation is to verify both:

  • Verify the webhook signature to prove the request came from your provider.
  • Treat the email content as untrusted input even after the webhook is verified.

For DKIM standards background, see RFC 6376.

Threat model: what you are defending against

When your system receives inbound mail as JSON via webhook, assume attackers will try common failure modes that also look like normal distributed-systems behavior:

  • Spoofing: sending requests directly to your webhook endpoint
  • Replay: re-sending a previously valid request to trigger side effects again
  • Body tampering: exploiting frameworks that re-serialize JSON and accidentally verify a different byte representation
  • Header confusion: relying on IP allowlists or “User-Agent” strings instead of cryptographic verification
  • Retry amplification: triggering multiple downstream actions because your handler is not idempotent

If your downstream consumer is an LLM agent, the stakes are higher: a forged or replayed email can become an instruction injection or cause an agent to resend verification emails, click links, or exfiltrate secrets.

What “verify webhook payload authenticity” should mean in practice

A secure webhook verification flow typically includes all of the following:

1) Verify a signature computed over the raw request body

The only byte sequence both parties can agree on is the raw HTTP request body. Many webhook breaches come from verifying a parsed object (or a pretty-printed JSON string) rather than the exact bytes received.

Implementation rules:

  • Capture the raw body bytes as your framework receives them.
  • Compute the expected signature using your webhook secret.
  • Compare signatures using a constant-time comparison.

How signatures are constructed varies by provider (HMAC, detached JWS, asymmetric signing). Do not guess header names or canonicalization rules.

Mailhook provides signed payloads for webhook notifications, but you should follow the exact contract in mailhook.co/llms.txt.

2) Enforce timestamp tolerance to reduce replay risk

A signature alone proves authenticity, but not freshness.

Add a timestamp requirement:

  • Require a provider-supplied timestamp header or field.
  • Reject requests older than a small window (commonly 5 minutes, sometimes 1 to 15 minutes depending on retries and queues).
  • Consider clock skew and set an explicit tolerance.

If you must support longer delays, combine timestamp checks with replay storage (next section).

3) Add replay detection using a delivery ID

Even legitimate providers retry webhooks. Attackers also replay.

So your handler should:

  • Extract a stable event identifier (delivery ID, event ID, message ID, or provider-specific idempotency key).
  • Store it with a TTL in a fast data store.
  • Reject or no-op duplicates.

This is not optional if your webhook triggers side effects (marking an OTP as consumed, creating an account, approving a workflow).

4) Fail closed, and separate “verification” from “processing”

A clean design is:

  • Verification layer: signature, timestamp, replay checks
  • Processing layer: parse JSON, dedupe by message/artifact, extract OTP or link, enqueue work

If verification fails:

  • Return a 4xx (often 401/403) and do not enqueue.

If processing fails:

  • Return a 2xx only after you have safely persisted the event for retry, or return a 5xx so the provider retries. Choose intentionally.

5) Keep secrets out of logs and protect the endpoint

Minimum hygiene:

  • Store webhook secrets in a secrets manager.
  • Rotate secrets and support dual validation during rotation.
  • Use HTTPS only.
  • Do not log raw signatures, secrets, or full email bodies in shared logs.

A provider-agnostic verification checklist (copy/paste for code reviews)

Use this as a review gate before you let agents or CI pipelines trust inbound email events:

  • Raw body is captured and used for signature verification
  • Signature verification is constant-time
  • Timestamp is required and checked with a defined tolerance
  • Replay protection exists using a stable delivery identifier
  • Handler is idempotent at the side-effect layer
  • Non-verified requests never reach downstream processors
  • Email content is treated as untrusted input (no HTML rendering, link allowlists, minimal artifact extraction)

Reference architecture: webhook-first with polling fallback

Webhook delivery is the lowest-latency way to receive inbound mail, but production systems should be resilient to occasional delivery failures.

A common pattern in automation-first inbox systems is:

  • Webhook triggers quickly when an email arrives.
  • Your handler verifies authenticity, persists a record, and enqueues work.
  • If the webhook is missed or delayed, your workflow can poll the inbox API as a fallback.

Mailhook supports both webhook notifications and a polling API, which is useful for deterministic QA and agent workflows where you want “push first, pull as backup”.

A simple flow diagram showing inbound email arriving via SMTP to a temp inbox provider, then a signed webhook POST to the customer endpoint, then verification (signature, timestamp, replay check), then persistence/queue, and finally an automation worker or LLM agent consuming a minimized JSON view.

Practical notes for LLM agents: authenticity is necessary, not sufficient

Even perfectly authenticated webhooks can deliver hostile content, because attackers can send real emails to your disposable inboxes.

For agent safety:

  • Prefer a minimized JSON view (subject, from, received_at, and extracted artifact), not raw HTML.
  • Avoid letting agents “browse” arbitrary links found in email.
  • Add host allowlists for verification links (and consider resolving redirects server-side with strict rules).
  • Put strict budgets on agent actions (no repeated “resend code” loops).

This is where structured JSON delivery helps: it makes it easier to build deterministic, minimal extractors instead of brittle HTML scraping.

Example pseudocode (structure, not provider-specific)

The goal is to show the control flow without inventing provider details.

raw_body = getRawRequestBodyBytes(req)
sig = req.headers["Signature"]
timestamp = req.headers["Signature-Timestamp"]

if !sig or !timestamp:
  return 401

if isTooOld(timestamp, toleranceSeconds=300):
  return 401

expected = computeSignature(secret, timestamp, raw_body)
if !constantTimeEquals(sig, expected):
  return 403

delivery_id = parseJson(raw_body).delivery_id
if seenRecently(delivery_id):
  return 200  // idempotent accept
markSeen(delivery_id, ttl=24h)

enqueueForProcessing(raw_body)
return 200

To implement this correctly for Mailhook (actual header names, how the signed string is constructed, and what IDs are available), follow the contract in mailhook.co/llms.txt.

Where Mailhook fits (without changing your security posture)

Mailhook is designed for workflows where email is an input to software systems, including LLM agents and QA automation:

  • Create disposable inboxes via API
  • Receive inbound emails as structured JSON
  • Get real-time webhook notifications (plus polling as a fallback)
  • Verify authenticity with signed webhook payloads
  • Use shared domains instantly, or bring a custom domain when you need tighter control

If you are building an “email as an event stream” pipeline, the critical takeaway is: treat your webhook like a public API surface. Verifying “email signed by” at the email layer is helpful, but verifying webhook payload authenticity is what protects your automations.

For implementation details, examples, and the canonical integration contract, start here: Mailhook llms.txt.

webhook-security email-automation DKIM authentication LLM-agents API-security

Related Articles