Testing webhook signature validation

Guides / Replay Lab

Signature verification is what stops anyone on the internet from sending your webhook endpoint fake events. It's also easy to get subtly wrong: verifying a parsed and re-serialized body instead of the raw bytes, comparing strings in a way that leaks timing, skipping verification when the header is missing, or ignoring Stripe's timestamp.

Three cases to test

hwcli replay evt_93df18ab --resign               # valid: must be accepted
hwcli replay evt_93df18ab --invalid-signature    # wrong: must be rejected
hwcli replay evt_93df18ab --remove-signature     # missing: must be rejected
  • --resign computes a fresh, valid signature over the body that is actually sent, using the signing secret stored for the endpoint (Endpoint settings → Signature). It works for Stripe and GitHub secrets, and also after you modify the body.
  • --invalid-signature changes the signature so it no longer matches, but keeps its format.
  • --remove-signature sends the request without the signature header.

Your handler should answer 2xx to the first and 400 or 401 to the other two. If an invalid signature gets a 200, your endpoint accepts forged events.

Your secret stays on the server

The signing secret is stored encrypted and is never shown to you, sent to hwcli or written to logs. hwcli asks the server for the signed request, and only the signature value is returned. The dashboard shows whether a request's signature is valid, invalid or absent.

As regression tests

name: reject-unsigned-payment
event:
  source: evt_93df18ab
signature: remove
expect:
  status: 401

Signature modes in tests are original, resign, remove and invalid. Tests with resign are signed again on every run, so Stripe's timestamp is always current.

Create an endpoint and capture your first webhook. No signup needed.

Start Free