How to upgrade Stripe API version with webhooks

Viewed 1498

I'm answering my own questions, but I figure I'd write this because I struggled quite a bit with this issue, and I'm also interested in hearing alternate answers.

The environment

We're using Stripe server-side for payments (one-time and subscriptions), and we're using Stripe webhooks to record payments and events in our DB. We have multiple servers behind a load balancer, and we use a canary/rolling deploy, so during deploy not all servers run the same code version.

The problem

When you want to upgrade the Stripe API version, there are often breaking model changes (e.g. new or renamed fields) that require code changes. And if you're not upgrading frequently, it can be a lot of changes! cough cough

There's a catch-22 during a rolling deployment:

  • if you wait until all servers are deployed with the new code/library to update the Stripe version in the Stripe dashboard, "new" servers may throw errors during the rollout because they're receiving webhooks in the old JSON format.
  • if you update the Stripe version before all servers are deployed, then it's the "old" servers that may throw errors because they're not compatible with the new JSON format.
  • if you update the Stripe version while you're deploying... you get a mix of both.

Stripe allows you to setup multiple webhook URLs, each with its own API version. But all matching events are sent to all webhook URLS (this wasn't clear to me at all at first).

There's also a mechanism in newer Stripe Java libraries to implement your own "exception handler" when the event model doesn't match, but it requires writing code and keeping track of every model change since the last upgrade, which in our case was... too many :)

Also with newer Stripe libraries, you cannot override the Stripe version: it's now hardcoded (which makes sense).

2 Answers

My solution to this problem is as follows:

  1. add a version parameter to the webhook URL in Stripe (e.g. https://example.net/stripe/webhook?version=2019-xx-yy with the API version currently used).
  2. in the webhook handler, before parsing the event JSON, compare the version URL parameter with the current library/code version: if they match, process the event. Otherwise, ignore it (deploy that change first).
  3. make sure the webhook handler keeps track of event ids already processed, and doesn't re-process them.
  4. upgrade the server code and library version to the new Stripe API version.
  5. before deploying the new code, add a second webhook URL in Stripe for the new version (e.g. https://example.net/stripe/webhook?version=2020-zz-tt). At this point, the servers will receive two copies of every event, but they will "drop" events in the new format because the version doesn't match.
  6. start canary/rolling deployment.
  7. during rollout, old servers will only process old events, while new servers will only process "new" events. Every event is sent twice, so step #3 is critical as it may not be desirable to process the same event more than once.
  8. once the rollout is complete, remove the old webhook URL and upgrade the API version in the Stripe dashboard.

This is pretty complex, and I feel like I'm overthinking this. There's also still a small chance some events will not be processed during the deploy (because both event versions go to servers that ignore it).

So I'm curious if anybody has a simpler solution.

Related