On 25 and 26 October 2026 I reviewed twelve open pull requests in payment, webhook and checkout code that other people maintain. None of them are mine, and I am not a maintainer of any of these projects. Each review has its own write-up with the problem, the proposed change, what I found, and what happened next, linked below.
The five patterns
1. A fix that quietly accepts more than before
The most common finding. The intent of the change is right, but a conversion around it widens what the code accepts.
- razorpay-node: decoding the signature as hex before the constant-time compare started accepting upper-case, truncated and padded signatures.
- Standard Webhooks: accepting any typed array signs
Uint16Arraydata as platform-endian bytes, and Python'sbytes(5)turns a wrong type into five zero bytes. - paypal-js:
"POSSIBLE" | ... | stringcollapses tostring, and an optionalorderIDbreaks every order integration. - stripe-react-native: a shipping type that still accepts
email, which the native SDKs drop.
2. Bytes and text are not the same thing
HMACs authenticate bytes. Header names are case-insensitive. Values can be lists.
- Standard Webhooks: signing the UTF-8 decoding of a binary body.
- Svix: PSR-7 header values are arrays, and
intval(['1700000000'])is1. - stripe-node: a plain object makes header lookups case-sensitive.
3. Order, retries and time
Webhooks arrive late, twice and out of order. State that is compared across events has to survive that.
- Better Auth: a retried event can skip
onTrialEndbecause a later event already updated the stored row. - Medusa: a total that counts unconfirmed refunds is wrong whenever two refunds interleave.
- n8n: keeping only the last signature breaks key rotation; existing workflows never receive a secret.
- Adyen Web: a listener left on
windowcompletes the next payment's challenge.
4. Tests that do not test what they claim
- razorpay-node: Mocha tests nested inside another test never run.
- paypal-js: a runtime test cannot check a type definition.
- Svix and stripe-node: fixtures that only use the convenient shape, so the bug cannot show up.
5. The change around the change
Descriptions, release notes, rollbacks and scope.
- Hyperswitch Web: a description that documents behavior the code does not have, and a security fix bundled with behavior changes.
- Payload: a change that is safe to deploy and unsafe to roll back while payments are in flight.
How I review
I read the change against the current code on the default branch, not only the diff, and I check claims before writing them: a regex, a PHP conversion, a Mocha behavior, a failed CI log. Where I could not confirm something, the review says so, and so does the write-up. I keep reviews specific: the line, the input that breaks, and the smallest change that fixes it. In one case the project's history shows that my suggestion had already been tried and rolled back in 2023; the Adyen write-up says so.
All twelve
- razorpay-node: constant-time webhook signatures
- paypal-js: typing onApprove per flow
- stripe-node: endpointFetch response headers
- stripe-react-native: shipping details and the API surface check
- Payload: Stripe metadata limit and a digest
- Better Auth: webhook ordering and lifecycle callbacks
- Medusa: the refund race
- Adyen Web: orphaned 3DS2 listeners
- Standard Webhooks: binary payloads
- Svix: case-insensitive headers
- n8n: Mailchimp webhook signatures
- Hyperswitch Web: postMessage origin checks
The outcomes in each post are dated. I will update them when the pull requests move.