Trust is earned, not given

A different perspective

2025-10-11 · Projects

DotNetCode, part 6: Verifying Stripe webhooks with hand-rolled HMAC

StripeWebhook (part of DotNetCode) is a generic Stripe webhook receiver as an isolated-worker Azure Function — and its best teaching moment is that it verifies Stripe's signature without using any Stripe SDK, implementing the HMAC-SHA256 check by hand.

How Stripe webhook signatures work

  1. You register one webhook URL and receive a signing secret.
  2. Every delivery carries a Stripe-Signature header: t=timestamp,v1=signature.
  3. The signature is HMAC_SHA256(secret, timestamp + "." + rawBody).

The receiver must use the raw bytes exactly as Stripe sent them — any re-serialisation (even pretty-printing the JSON) changes the bytes and the signature check fails. That is why the function reads the body unmodified before anything else, and rejects missing/invalid signatures with HTTP 400 before parsing.

Why hand-roll it?

Not for pride — for understanding. HMAC is a handful of lines over System.Security.Cryptography, and implementing it once teaches what "signing" actually means: a keyed hash over a canonical byte sequence, plus a timestamp window to defeat replay attacks. After this, every SDK's verification call is transparent rather than magic.

The dispatch design: log, don't act

After verification, the function parses only id and type, runs the type through a switch grouped by category covering a broad catalog of common Stripe events — and every branch is a deliberate no-op placeholder that logs the category. Returning HTTP 200 quickly matters (Stripe retries non-200s with backoff); the real work belongs in your own handlers, and this project gives you the verified, trusted event to build them on. Client samples included can build and sign test events so you can exercise the endpoint locally.

Repository: github.com/bobhuang1/DotNetCode/tree/master/StripeWebhook