Skip to content

Verifying signatures

Every webhook delivery includes a signature header:

X-3AVA-Signature: t=1714000000,v1=5d9f...

t is the Unix timestamp the signature was generated at; v1 is HMAC-SHA256 of {t}.{raw_request_body} using your webhook’s signing_secret.

Without verification, anyone who knows your webhook URL can POST to it and impersonate 3AVA Mail. Verification proves the request actually came from us.

import hmac
import hashlib
import time
SIGNING_SECRET = "whsec_XnK8pQrS..."
TOLERANCE_SECONDS = 300 # 5 min
@app.post("/webhooks/amdy")
def webhook():
sig_header = request.headers.get("X-3AVA-Signature", "")
parts = dict(p.split("=", 1) for p in sig_header.split(","))
t = int(parts["t"])
received = parts["v1"]
# Reject old signatures (replay protection)
if abs(time.time() - t) > TOLERANCE_SECONDS:
return "stale", 400
# Recompute and compare
payload = f"{t}.{request.get_data(as_text=True)}"
expected = hmac.new(
SIGNING_SECRET.encode(),
payload.encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, received):
return "bad signature", 400
# Process the verified event
event = request.get_json()
handle_event(event)
return "", 200

Log it, return 400, and do not process the payload. If failures spike, your secret may have leaked — rotate it by deleting the webhook and creating a new one.

The t (timestamp) check rejects requests older than 5 minutes. This makes a captured-and-replayed request unusable after that window even if the attacker has the body.