Webhook signature design looks like a simple choice between HMAC-SHA256 and Ed25519 until the rotation, multi-algorithm support, and verification ergonomics get involved. The choice that looks adequate at launch becomes the choice that constrains every subsequent security improvement. Most providers ship with one algorithm and a fixed key; the providers that customers trust over years of operation have a pipeline that survives algorithm migration without breaking deployed integrations.
What signing buys
A webhook signature proves that the payload came from a sender who held the signing key and has not been tampered with in transit. The signature does not prove the payload is current (replay protection requires a timestamp), does not prove the payload was sent to the intended receiver (preventing misrouting requires the URL or receiver-ID to be included in the signed material), and does not prove the payload is what the sender intended to send at the application level (semantic verification is the receiver's responsibility).
The signing primitives are well-established. HMAC-SHA256 provides symmetric authentication with a shared secret. Ed25519 provides asymmetric authentication with a private signing key and a public verification key. Both algorithms are fast, well-implemented in standard libraries, and free of known weaknesses at standard parameter sizes. The choice between them is operational rather than cryptographic.
HMAC as the right default
HMAC-SHA256 is the right default for B2B SaaS webhooks because the operational simplicity matches the use case. The customer holds the same secret the sender uses, so the signature verification is a single function call with a single input. The secret can be displayed in the dashboard and copied into the receiver's environment. The customer can rotate the secret unilaterally by generating a new one and updating both sides.
The disadvantages of HMAC are real but manageable. The secret must be transmitted to the customer at least once, which means it appears in the dashboard and in the customer's logs. The secret cannot be revoked centrally; the sender has to stop using it. The same secret authenticates both directions if the receiver also signs anything, which can leak across boundaries. None of these are showstoppers for B2B SaaS at the scale where the customer is the operator.
Ed25519 as the right answer for marketplace scale
Ed25519 is the right answer when the sender does not want to know what the receiver knows. The sender holds a private signing key that never leaves the sender's infrastructure. The receiver holds a public verification key that can be published openly. Compromise of the receiver's environment does not expose the sender's signing key.
The asymmetric model fits marketplaces and platform integrations where the same sender authenticates to many receivers and the receivers may have varying security postures. AWS SNS uses asymmetric signing for this reason. The trade-off is operational complexity: the receiver needs a way to fetch and refresh the public key, the sender needs a key rotation procedure that publishes new public keys before retiring old ones, and the verification code has to handle the multi-key state.
The signature format
The signature should be carried in a header with the algorithm identifier embedded. Stripe uses the format t=timestamp,v1=signature where v1 identifies the algorithm version. GitHub uses sha256=signature where the prefix identifies the algorithm. Both formats let the receiver dispatch to the correct verification routine and let the sender migrate to new algorithms without breaking old receivers.
The signed material should include the timestamp and the request body. Including the timestamp prevents simple replay attacks; the receiver can reject signatures whose timestamp is more than a few minutes old. Including the body prevents tampering; the signature is invalid if any byte of the body changes in transit. Including the receiving URL or receiver ID prevents misrouting attacks where a signed payload sent to one endpoint is replayed to another.
The receiver should verify the signature against the raw body bytes, not the parsed body. JSON parsing can change byte representation (whitespace, key order, number formatting), which invalidates the signature. The middleware order matters: signature verification has to happen before any body parsing.
The rotation pipeline
The signing secret needs to be rotatable without breaking deployed receivers. The pattern is multiple active secrets per endpoint with explicit lifecycle metadata. The webhook_signing_secrets table holds (endpoint_id, secret_id, secret_value, created_at, active_from, active_until, status) with multiple rows per endpoint during the rotation window. The sender uses the active-from-most-recent secret for new deliveries; the receiver tries all active secrets in order and accepts any match.
The rotation procedure is generate-new-secret then activate-new-secret then notify-customer then wait-for-customer-deployment then deactivate-old-secret. The wait window is typically 24 to 48 hours for B2B SaaS, which matches the typical operational rhythm of customer infrastructure changes. The notification surface is email plus dashboard banner plus webhook event when supported. The customer-facing message emphasizes that no action is required if the customer follows the standard verification pattern; the new secret is automatically accepted alongside the old one during the window.
The retired secret should not be deleted from the database for some retention period; it is needed for the audit trail showing which secret was used for past deliveries. The retention period matches the audit log retention, typically 90 days hot plus longer cold tier. The status column distinguishes active, retiring, and retired states for operational reporting.
The algorithm migration pipeline
The algorithm migration pipeline is the rotation pipeline extended one dimension. The webhook_signing_secrets table gains an algorithm column. The sender signs with the most recent active secret and its associated algorithm; the receiver verifies any active secret with its algorithm. The migration adds a new algorithm in parallel with the old one and runs both during a longer transition window before retiring the old algorithm.
The migration window for algorithm changes is months, not days. Customers need time to upgrade their verification code to handle the new algorithm. The window allows the customer to verify either the old or the new algorithm during the transition and to fully migrate to the new one before the old is retired. The deprecation notice in the changelog and the explicit headers on each delivery (X-Webhook-Signature-Algorithm or similar) telegraph the upcoming change.
The asymmetric-to-asymmetric or HMAC-to-Ed25519 transition is the longest because it changes the verification model. The customer-facing migration guide has to walk the customer through the new verification code and the new key acquisition pattern. The sender-side dual-signing during the transition window is operationally more complex than dual-key-same-algorithm but is feasible with modest engineering investment.
The header surface
The signing-related headers should include the signature itself, the timestamp, the algorithm identifier (or version), and optionally the key ID used to sign. The key ID lets the receiver pick the correct verification key without trying all of them, which is helpful when the receiver has multiple senders or when key rotation is in progress. The Webhook-Id header identifies the specific delivery for idempotency tracking.
The headers should be normalized to a documented case and a documented order for the signed material. Some HTTP libraries normalize headers in transit, which can break signature verification if the signed material includes headers. The right pattern is to sign only the body and the timestamp, with the URL or receiver-ID as an out-of-band parameter, which avoids the header normalization issue entirely.
What signature verification does not prove
The signature does not prove the message is fresh. Replay protection requires the receiver to track recently-seen signatures or to enforce a tight timestamp window. The five-minute window is the conventional choice for B2B SaaS, which balances clock skew tolerance against replay vulnerability.
The signature does not prove the message arrived in order. Webhook delivery is unordered in general, and the signature has no sequence information. Out-of-order delivery is the receiver's problem to handle via timestamps or version numbers in the application layer.
The signature does not prove the receiver should process the message. Authorization is a separate concern from authentication. The signature proves the sender is who they claim to be; whether the message represents something the receiver should act on is application-level logic.
Our use across the four products
DocuMint uses HMAC-SHA256 for outbound webhooks (Stripe receives our signed payloads on the receiver side, and we follow the Stripe convention on our own outbound webhooks). The signing-secret pipeline supports multiple active secrets per endpoint with 24-hour transition windows.
CronPing uses HMAC-SHA256 for monitor-event webhooks. The schema matches DocuMint's; the rotation tooling is shared.
FlagBit uses HMAC-SHA256 for flag-change webhooks. The smaller delivery volume makes the rotation overhead relatively expensive but the same shared infrastructure handles it.
WebhookVault is the most webhook-shaped product and has the most-developed signature infrastructure including bulk secret rotation and per-endpoint algorithm pinning. The roadmap includes Ed25519 support as an opt-in for customers who want asymmetric verification, with HMAC-SHA256 remaining the default.
The deeper observation is that signature design is one of the API surfaces where the right pattern is mostly about supporting eventual migration rather than picking the optimal choice now. The HMAC-SHA256 default is good enough that the migration to Ed25519 is rarely urgent; the infrastructure to support that migration when it becomes useful is what distinguishes thoughtful design from reactive bolt-on. The customers who survive long enough to need the migration are the customers worth investing in.
Our products: DocuMint (PDF invoice generation API), CronPing (cron job monitoring with status pages), FlagBit (feature flags API for modern teams), and WebhookVault (webhook capture and replay) put these patterns into production.