Webhook Trip Hazards

Posted on

Want to read this the hard way? Enjoy as a tweet thread.

Want to watch a video instead? Here's one about handling Stripe Webhooks?

Sometimes, users ask why Stripe webhook signature verification fails. Here's a list of the most common reasons we see and how to fix them 👇.

Background context

First, some background. When Stripe sends webhook event notifications, the POST request headers include a signature to prove the event came from Stripe. You can automatically verify signatures to trust the event's content vs. retrieving it through the API. Here are the official docs showing how to implement signature verification with the client libraries.

Different signing secret

The webhook signing secret used in the webhook handling code is not the same as shown in your Stripe dashboard for that webhook endpoint. Each webhook endpoint configured in the dashboard or for the Stripe CLI has a unique signing secret.

If you are using Stripe Connect, you'll likely have at least two webhook endpoints, one for your account's events and one for connected accounts' events. Each has a different signing secret, and the webhook handler might only be using one.

Modified payload

The payload in the webhook handler is different from the data Stripe signed. Often, the web framework or a library tried to be helpful in middleware and may have:

For signatures to match, the webhook handler must use the exact same string as Stripe. Ensure you're working with the HTTP request's raw body without any interference by the framework or any libraries.

For instance, stripe-node with bodyParser override:

https://carbon.now.sh/AqUXy7XGQQdgxbpjl16v

Depending on the node framework, a workaround in this GitHub issue might also be helpful.

The webhook handler's string encoding for the payload is not set to UTF-8. The Stripe API treats all strings as UTF-8. The signature header is calculated from a UTF-8 encoded string on the Stripe side. Enforce UTF-8 for that payload in your webhook handler.

Using ASCII sometimes trips up folks working in PHP. Additionally, when working with stripe-python you'll likely want to decode as utf-8:

https://carbon.now.sh/UFrXVLmvljQ39R25qikI

Handling webhooks with #AWS Lambda? You might need some custom logic to access the raw request body.

In API Gateway, set up a Body Mapping Template with Content-Type: application/json (just this – don't include ; charset=utf-8 afterwards):

https://carbon.now.sh/ar8yXN77uSxCbXAA2Xjl

Missing signature

The signature is not in the request headers. In an older implementation of webhook notifications, signatures weren't included in the request headers unless the endpoint's secret was revealed in the dashboard. Now, Stripe sends the signature headers for all event notifications.