Journal
Write-ups of investigations, architecture and tradeoffs. Facts, hypotheses, decisions and test results are marked as such.
Twelve payment and webhook code reviews: the bugs that keep coming back
In October 2026 I reviewed twelve pull requests in Stripe, PayPal, Razorpay, Adyen, Payload, Medusa, Better Auth, Svix, Standard Webhooks, n8n and Hyperswitch code. Most findings fall into five patterns.
Code review
Stripe webhook ordering in Better Auth: how a retry can drop onTrialEnd
Refetching the subscription is the right fix for Stripe snapshot events. Deriving lifecycle callbacks from the event while guarding them with the stored row meant a retried event could skip onTrialEnd, onTrialExpired and onSubscriptionCancel.
Code review
Payload's Stripe adapter and the 500-character metadata limit: keep the check, store a digest
Payload ecommerce put the whole cart into Stripe PaymentIntent metadata, so checkout failed at about eight items. Removing it fixed checkout and removed the only item-drift check; a 64-character hash keeps both.
Code review
HMAC signs bytes, not text: binary webhook payloads in Standard Webhooks
The JavaScript and Python Standard Webhooks libraries decoded the body as text before signing it, so gzip, protobuf or image payloads could not be verified. The fix is right; the review is about the inputs it now accepts silently.
Code review
Constant-time webhook signatures in razorpay-node: when the fix accepts more than the bug did
A pull request replaced === with crypto.timingSafeEqual in razorpay-node. Decoding the signature as hex made the check accept upper-case, truncated and padded signatures, and the new tests never ran.
Code review
Typing PayPal Card Fields onApprove per flow instead of making orderID optional
A paypal-js pull request made orderID optional to support vault flows, a breaking type change for every order integration. Typing onApprove per branch of an existing union fixed it, and the author adopted the suggestion.
Code review
stripe-node endpointFetch headers: passing them through is not the same as using them
A stripe-node change stops the endpointFetch HTTP client from discarding response headers. Two follow-ups: retry-after does not affect backoff, and a plain object makes header lookups case-sensitive.
Code review
stripe-react-native shipping details: unblocking a failing API surface check
A fix for missing carrier, phone and tracking number in stripe-react-native shipping details was stuck on a failing CI check. The remaining diff was not in the pull request at all, and the type still accepted a field that the native SDKs drop.
Code review
Case-insensitive webhook headers in the Svix PHP and Ruby SDKs, and the PSR-7 array trap
A Svix fix lets verify() find Svix-Id as well as svix-id. With PSR-7 headers the keys now match but the values are arrays, and intval turns the timestamp into 1.
Code review
Signing Mailchimp webhooks in n8n: key rotation and the workflows that never get a secret
n8n adds X-Mailchimp-Signature verification to its Mailchimp trigger. Two non-blocking points: keep every v1 signature for rotation, and existing workflows silently stay unverified.
Code review
postMessage origin and source checks in the Hyperswitch web SDK, and why the fix should ship alone
Hyperswitch Web validated neither origin nor source before confirming a payment from a message. The sender checks are solid; the pull request also bundles behavior changes and describes features that are not in the code.
Code review
A refund race in Medusa: why counting pending refunds overstates refunded_amount
Medusa recomputes the refunded amount after a provider rejects a refund, which fixes the race the test forces. The total still counts unconfirmed refunds, and the recompute in the catch block can hide the provider error.
Code review
Orphaned 3DS2 postMessage listeners in Adyen Web, and a detached-node check that misses shadow DOM
A Drop-in torn down without unmount leaves a window message listener behind, so a later 3DS2 challenge completes twice. The guard uses document.contains, which is always false inside a shadow root.
Code review