Here are a few lessons I learned while working with Stripe Webhooks in production.
ā”š¬š¼ššæ šš§š§š£ ššš®ššš š°š¼š±š² š¶š šš¼ššæ šæš²ššæš š½š¼š¹š¶š°š.
Returning 200 tells Stripe the event was received and processed successfully. Stripe won't retry it again.
If your business logic fails, you should return a 5xx status code so that Stripe automatically retries the event delivery
ā”š§šµš² š²šš²š»š šš¼š šæš²š°š²š¶šš² š¶š š® šµš¶š»š, š»š¼š ššµš² ššæšššµ.
Webhook events may arrive late or out of order. Don't trust the payload. Refetch the current state from a source of truth and act accordingly.
ā”šš¼š»'š š±š¼ šš¹š¼š šš¼šæšø š¶š»šš¶š±š² ššµš² šŖš²šÆšµš¼š¼šø š²šš²š»š.
Stripe has a timeout threshold. If the endpoint doesn't respond quickly enough, Stripe considers it a failure and retries.
Process the slow work asynchronously outside the webhook handler.
Do you have any hard-learned lessons about Webhooks or Stripe? I'd love to know about that.
I share more engineering notes and production lessons on LinkedIn: Dipendra Neupane.
Top comments (3)
Strong framing. In B2B EDI over AS2 we live this same problem wearing older clothes: when a functional acknowledgment (or MDN) goes missing, the sender can't tell whether the document processed or just vanished ā so both sides need explicit acknowledgment semantics beyond the HTTP status. Separating ack from processing is the only approach that has ever actually worked for us.
good list. one i learned the hard way: returning 5xx is only safe if the retry can't double-apply. if the handler wrote half the access change before it threw, the retry runs on top of that. we record the event id in the same transaction as the access change, so a retry finds either nothing done or everything done. also worth testing what happens when stripe's retries run out, because after that nothing tells you again.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.