Webhook · Security · HMAC
Verifying incoming webhook signatures: Shopify, eBay and Stripe
A webhook is an HTTP request from the internet: before trusting its content you must verify its signature, otherwise anyone can send fake events to your endpoint. Shopify, eBay and Stripe do this similarly in principle but differently in detail. This guide compares the three approaches with a .NET example on Azure.
The shared rule
- compute the signature over the raw body (the received bytes), not the parsed JSON object;
- compare with a constant-time comparison to avoid timing attacks;
- if it doesn't match respond
401and do nothing; if it matches respond200and process asynchronously; - keep the signing secret out of the code.
.NET example (HMAC-SHA256)
// rawBody = the EXACT request bytes (no re-serialisation)
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(signingSecret));
byte[] computed = hmac.ComputeHash(rawBody);
byte[] received = Convert.FromBase64String(headerValue); // Shopify: base64
bool ok = CryptographicOperations.FixedTimeEquals(computed, received); // constant time
if (!ok) return Results.Unauthorized();
// ... queue the processing and respond 200
For Stripe the signature is hex and is computed over timestamp + "." + payload; the rest of the flow is identical.
Per-platform differences
| Platform | Header | Mechanism |
|---|---|---|
| Shopify | X-Shopify-Hmac-Sha256 | Base64 HMAC-SHA256 with the client secret, over the raw body |
| Stripe | Stripe-Signature | Hex HMAC-SHA256 over timestamp+payload, with timestamp tolerance |
| eBay (account deletion) | x-ebay-signature | SHA-256 hash for the challenge and signature validation with eBay's public key |
So Shopify and Stripe use a shared secret (HMAC); eBay, for account deletion notifications, uses a signature verifiable with a public key. The eBay flow is detailed in the guide on the eBay account deletion webhook, while for Shopify webhooks see the guide on Shopify GDPR compliance webhooks.
Keep the secrets safe
Verification only works if the secret stays secret: keep it in a secret manager with a managed identity, as described in securing secrets and credentials with Azure Key Vault, and rotate it periodically.
Common mistakes
- computing the signature over the parsed JSON instead of the raw body;
- using a normal string comparison instead of a constant-time one;
- ignoring the timestamp tolerance (Stripe) and accepting old requests (replay);
- hardcoded signing secret in code or logs.
Conclusion
Verifying signatures is a few lines of code, but getting them wrong exposes the endpoint to fake events. The recipe is always the same: raw body, constant-time comparison, 200/401, protected secret. References: HTTPS webhooks (Shopify) and webhook signatures (Stripe).