Which Headers Does AhaSend Send?
All webhook and route requests include Standard Webhooks security headers for verification:webhook-id: Unique identifier for the webhook event (used as idempotency key)webhook-timestamp: Unix timestamp when the webhook was sentwebhook-signature: HMAC signature of the payload using your webhook secret
id.timestamp.body, following the
Standard Webhooks specification.
The easiest path is a verifier that already implements this correctly, such as
the official AhaSend SDKs. Whatever you use, verify
against the raw request body bytes (before any JSON parsing), and reject
deliveries whose webhook-timestamp is outside your tolerance window in either
direction to prevent replay.
Which Secret Should I Use?
Webhook secrets start withaha-whsec-; route secrets start with aha-rsec-. Use the entire secret, including its prefix, as the HMAC-SHA256 key. Do not base64-decode it.
The signed input is the delivery ID, a dot, the Unix timestamp, a dot, and the exact raw request body. The webhook-signature header contains v1,<base64 signature> values separated by spaces; accept a delivery if any supported signature matches. The recommended timestamp tolerance is five minutes in either direction, matching the AhaSend SDK default.
The webhook-id header identifies a delivery. Payload webhook_id identifies the configured webhook, and route_id identifies a route. Deduplicate the header value only after verification, atomically with durable work. Return 2xx for verified unknown event types and already-processed deliveries.
Runnable Verification Examples
For a route delivery, setAHASEND_WEBHOOK_SECRET in these examples to that route’s aha-rsec- secret; the verification code is the same. For an email event, use that webhook’s aha-whsec- secret.
Save an incoming raw body as payload.json without reformatting it. Set AHASEND_WEBHOOK_SECRET, WEBHOOK_ID, WEBHOOK_TIMESTAMP, and WEBHOOK_SIGNATURE to the secret and headers from that same delivery. Run the check while its timestamp is within five minutes. These scripts print only whether verification succeeded.
Install the package used by your chosen example: npm install @ahasend/sdk@0.2.1 or npm install standardwebhooks for Node.js; go mod init example.com/verify followed by go get github.com/AhaSend/ahasend-go or go get github.com/standard-webhooks/standard-webhooks/libraries/go for Go; python3 -m pip install standardwebhooks for Python. PHP below uses its built-in HMAC functions.
base64(HMAC-SHA256(secret_utf8, id + "." + timestamp + "." + body)) directly. It avoids the current Standard Webhooks PHP fromRaw constructor issue. See the upstream Python and PHP implementations.
In an HTTP handler, enforce the 30,000,000-byte limit while reading the stream, before buffering. Keep a lower limit if your hosting platform requires one. SDK adapters already bound their reads; see the Node.js SDK and Go SDK examples.

