How to Authenticate Your Webhook Endpoint

When email-webhook fires your endpoint, the request comes from the internet with no built-in signature. This guide explains the two layers of protection available and how to validate them in your receiving server.

Layer 1: Keep your email address secret

Your incoming address — abc123xyz@email-webhook.com — is the first line of defence. The local part is a system-generated opaque ID, not a guessable name. Anyone who knows it can trigger your webhooks, so treat it like a password: don't commit it to public repos, don't paste it in public docs.

If you suspect the address has leaked, delete your webhooks and create new ones to get a fresh address.

Layer 2: Custom authentication headers

The second layer is a secret header that your endpoint checks on every request. You add it once in the dashboard; email-webhook sends it with every delivery.

In the webhook form, open the Custom Headers section and add a key-value pair:

Header name Header value
Authorization Bearer your-secret-token

Or equivalently with an API key header:

Header name Header value
X-Api-Key your-secret-token

You can add up to 5 custom headers per webhook.

Validating the header in your server

Node.js (Express)

app.post("/webhook", (req, res) => {
  if (req.headers["authorization"] !== "Bearer your-secret-token") {
    return res.sendStatus(401);
  }
  const { from, subject, message } = req.body;
  // handle email...
  res.sendStatus(200);
});

Python (Flask)

@app.post("/webhook")
def handle_email():
    if request.headers.get("Authorization") != "Bearer your-secret-token":
        return "", 401
    data = request.get_json()
    # handle email...
    return "", 200

Ruby (Sinatra)

post "/webhook" do
  halt 401 unless request.env["HTTP_AUTHORIZATION"] == "Bearer your-secret-token"
  data = JSON.parse(request.body.read)
  # handle email...
  status 200
end

Return 401 (or any non-2xx status) and email-webhook records the delivery as failed. The request is not retried if the status code is 4xx.

On bearer tokens

Generally, prefer an API key over a bearer token. If you do need a bearer token, make sure it has a long enough duration (usually encoded as the exp parameter) so that your webhook requests succeed over time. You can learn more about bearer token at https://jwt.io

Using `X-email-webhook-id` as an idempotency key

Every delivery includes an X-email-webhook-id header containing a UUID unique to that request. Store it on the records you create and reject duplicates — this protects you if your server returns a non-2xx status but the processing already completed.

app.post("/webhook", async (req, res) => {
  if (req.headers["authorization"] !== "Bearer your-secret-token") {
    return res.sendStatus(401);
  }
  const deliveryId = req.headers["x-email-webhook-id"];
  if (await alreadyProcessed(deliveryId)) {
    return res.sendStatus(200); // acknowledge without re-processing
  }
  // handle email...
  res.sendStatus(200);
});