# F4 > F4 is the engineering record of Ratomir Jovanovic: projects, technical decisions, failures and fixes, each linked to the code that supports it. F4 is the personal engineering site of Ratomir Jovanovic. GitHub: https://github.com/ratomirjovanovic. Every case study and problem record links to the issues, pull requests and files that support it. ## Projects - [PandaPayLens](https://www.f4.rs/work/pandapaylens): An open-source, local-first toolkit that decodes payment provider errors, webhooks and event logs, rebuilds the transaction timeline and explains what looks wrong, without sending the data anywhere. ## Problem records - [The first npm release and trusted publishing](https://www.f4.rs/problems/first-npm-release-with-trusted-publishing): npm trusted publishing can only be configured for a package that exists, so the first version had to be published by hand, which would then make the release workflow fail. - [Documentation links that were only broken from CI](https://www.f4.rs/problems/geo-dependent-404-in-link-checker): The first scheduled link check reported 14 broken Razorpay links. They load from Europe but answer 404 to GitHub-hosted runners. - [A shared id prefix made Checkout.com a false alternative](https://www.f4.rs/problems/shared-id-prefix-false-provider-match): A record whose only Checkout.com trait was a pay_ id scored higher than a loosely matching record with documented fields, so every Razorpay payment listed Checkout.com as an alternative. - [A refund delivered twice looked like two refunds](https://www.f4.rs/problems/duplicate-webhook-without-delivery-id): Providers that send notifications without a delivery id defeated duplicate detection, so a repeated refund notification could be read as refunds exceeding the payment. - [The timeline showed late payment steps first](https://www.f4.rs/problems/untimed-events-sorted-to-the-end): Events without a timestamp were sorted after every timed event, so a Paystack flow appeared with the webhook and verification before the PIN and OTP steps. - [Redaction regexes that ran in quadratic time on long text](https://www.f4.rs/problems/linear-time-redaction-in-payment-text): Two patterns used to hide email-like values in provider text backtracked from every position on long input without whitespace. Both were replaced with bounded, linear-time alternatives. ## Journal - [Twelve payment and webhook code reviews: the bugs that keep coming back](https://www.f4.rs/journal/twelve-payment-and-webhook-code-reviews): 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. - [Stripe webhook ordering in Better Auth: how a retry can drop onTrialEnd](https://www.f4.rs/journal/better-auth-stripe-webhook-ordering-callbacks-review): 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. - [Payload's Stripe adapter and the 500-character metadata limit: keep the check, store a digest](https://www.f4.rs/journal/payload-ecommerce-stripe-metadata-limit-review): 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. - [HMAC signs bytes, not text: binary webhook payloads in Standard Webhooks](https://www.f4.rs/journal/standard-webhooks-binary-payload-signing-review): 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. - [Constant-time webhook signatures in razorpay-node: when the fix accepts more than the bug did](https://www.f4.rs/journal/razorpay-node-constant-time-webhook-signature-review): 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. - [Typing PayPal Card Fields onApprove per flow instead of making orderID optional](https://www.f4.rs/journal/paypal-js-card-fields-onapprove-types-review): 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. - [stripe-node endpointFetch headers: passing them through is not the same as using them](https://www.f4.rs/journal/stripe-node-endpointfetch-response-headers-review): 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. - [stripe-react-native shipping details: unblocking a failing API surface check](https://www.f4.rs/journal/stripe-react-native-shipping-details-review): 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. - [Case-insensitive webhook headers in the Svix PHP and Ruby SDKs, and the PSR-7 array trap](https://www.f4.rs/journal/svix-case-insensitive-webhook-headers-review): 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. - [Signing Mailchimp webhooks in n8n: key rotation and the workflows that never get a secret](https://www.f4.rs/journal/n8n-mailchimp-webhook-signature-review): 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. - [postMessage origin and source checks in the Hyperswitch web SDK, and why the fix should ship alone](https://www.f4.rs/journal/hyperswitch-web-postmessage-origin-validation-review): 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. - [A refund race in Medusa: why counting pending refunds overstates refunded_amount](https://www.f4.rs/journal/medusa-refund-race-refunded-amount-review): 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. - [Orphaned 3DS2 postMessage listeners in Adyen Web, and a detached-node check that misses shadow DOM](https://www.f4.rs/journal/adyen-web-3ds2-duplicate-challenge-completion-review): 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. ## Topics - [Code review](https://www.f4.rs/topics/code-review): Reviews of payment, webhook and security changes in other open-source projects: what was wrong, what was proposed, and what happened. - [Payments](https://www.f4.rs/topics/payments): Payment provider integrations, error semantics, transaction lifecycles and reconciliation. - [Security](https://www.f4.rs/topics/security): Input handling, redaction, Content Security Policy and supply-chain controls. - [Testing and CI](https://www.f4.rs/topics/testing): Property tests, fixtures, browser tests and release automation. - [TypeScript](https://www.f4.rs/topics/typescript): Type-safe engines, monorepos and tooling. - [Webhooks](https://www.f4.rs/topics/webhooks): Delivery, duplication, ordering and verification of provider notifications. ## Open source - [ratomirjovanovic/PandaPayLens](https://github.com/ratomirjovanovic/PandaPayLens): Open-source payment error decoder and transaction flow visualizer for Stripe, Adyen, payabl. and more. - [ratomirjovanovic/ratomirjovanovic](https://github.com/ratomirjovanovic/ratomirjovanovic): Profile README ## Optional - [About](https://www.f4.rs/about): Background of Ratomir Jovanovic. - [Activity](https://www.f4.rs/activity): Public GitHub activity, grouped by repository and day. - [RSS feed](https://www.f4.rs/feed.xml): Journal posts and problem records.