> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Verifying Webhook Signatures

> Make sure the pings you receive are actually from Mailofly — and not some random person pretending.

# Verifying Webhook Signatures 🔐

When Mailofly sends a webhook to your server, anyone on the internet *could* send a fake request to your same URL. How do you know it's really us?

Simple: we sign every request with a secret key. You check the signature before trusting the data. If the signature matches — it's legit. If not — ignore it.

***

## What we include in every request

Every webhook POST has two special headers:

* `mailofly-signature` — an HMAC-SHA256 signature we computed using your secret + the request body
* `mailofly-timestamp` — when we signed it (Unix timestamp in seconds)

***

## How to verify (Node.js example)

```typescript theme={null}
import * as crypto from "crypto";

export function verifyMailoflyWebhook({
  payload,
  signature,
  timestamp,
  secret,
}: {
  payload: string; // ⚠️ Must be the RAW request body string, not parsed JSON
  signature: string;
  timestamp: string;
  secret: string; // Your whsec_... signing secret from the dashboard
}): boolean {
  // Block replay attacks: reject anything older than 5 minutes
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
    return false; // Too old, could be a replay attack
  }

  // Recreate the signature
  const signedContent = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(signedContent)
    .digest("hex");

  // Compare safely (prevents timing attacks)
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}
```

***

## Important: use the raw body!

> ⚠️ **Don't parse the JSON before verifying.** If you do, whitespace might change and the signature will look wrong even though it's real. Read the raw bytes from the request and verify *those*.

In Express, this means using `express.raw({ type: 'application/json' })` instead of `express.json()` for your webhook route.

***

## Where to find your signing secret

1. Go to **Settings** → **Webhooks** in your dashboard
2. Click on the webhook you created
3. Copy the **Signing Secret** — it starts with `whsec_`

Keep it secret! Don't put it in your code directly — use an environment variable.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.