Home > Software design >  Handling concurrent webhooks in Firestore
Handling concurrent webhooks in Firestore

Time:01-05

I have a firestore app that's receiving webhooks from Stripe and writing the data from the webhook to a document.

Sometimes, multiple webhooks for the same Stripe object are sent less than a second apart. For example, two Charge objects with different payment statuses are sent immediately after one another. Firestore doesn't always write these in the correct order, so in some cases the status in my database is the status from the earlier webhook and not the latest.

How can I guarantee that firestore writes the documents in the correct order? There's a timestamp on the stripe webhook so it's possible to tell which is more recent.

I was going to write a transaction that only writes the data if the date on the webhook is greater than the date of the document currently in Firestore. Is there a better way to do this, though, or do I need to wrap every one of my webhook handlers in a transaction to ensure Firestore processes them in the correct order?

Here's the basic form of the stripe webhook:

{
  "id": "evt_1MMIvs2fEBYORq3P6PcXHdkj",
  "object": "event",
  "api_version": "2022-08-01",
  "created": 1672784768,
  "data": {
     "id": "ch_3MMMiLK6jt4dQclj1RAH3dk9",
     "status": "succeeded",
     ...
  }
}

CodePudding user response:

do I need to wrap every one of my webhook handlers in a transaction to ensure Firestore processes them in the correct order?

Transactions don't guarantee a specific ordering. They just guarantee that each write will occur atomically and that they operate on a consistent view of all documents participating in the transaction - none of the documents in the transaction will change outside of the transaction as long as all changes are being done with other transactions.

In fact, it's impossible to guarantee the ordering that you're looking for since the webhooks and their database queries can appear to start or complete in any order on your backend.

The easiest thing to do is not update a single document with each webhook invocation. Instead, write an entirely new document for each status update, creating a log of what happened. If you want to get the most recent charge status at any give moment, query for the document with the latest timestamp that Stripe provides (or some other indicator of order that they give you).

With a log like this, you will also be able to look back and know when and how everything that happened with each charge, which has diagnostic value perhaps beyond what you're looking for right now, and might be helpful for customer service reasons.

If you must update a single document, then:

  1. Start a transaction on the document by getting it
  2. Check the Stripe timestamp on it
  • If the document timestamp is less than the one in the latest webhook, update the document with the latest data.
  • Otherwise ignore the update and terminate the transaction early.
  • Related