The tiny local rate‑limit proxy that found our payment throttling

How I built a small local proxy to simulate third‑party rate limits for payment flows, the bug it caught, and the false confidence I had to unlearn.

Written by: Arjun Malhotra

A laptop on a wooden desk with code on the screen and a smartphone nearby
Photo by Brooke Cagle on Unsplash

It was 10:30 p.m. and a client on our payments team had just messaged: “We missed three payouts in the last hour — gateway returning 429s.” My laptop was open, my dinner cold, and the staging environment looked fine. In production, the payment gateway had started throttling us in a way our test harness never reproduced.

We spend a lot of time mocking happy paths. We don’t spend enough time mocking being told “slow down”. That night I built a tiny local proxy to do exactly that: pretend the third‑party API was fed up and start returning 429s, with the patterns and backoff I needed to debug our retry and billing code.

What I built (in ten minutes)

Why this was useful immediately

An example that mattered One flow was critical: a reconciliation job that retried failed payouts. Locally, with a 3‑retry policy and exponential backoff, it looked fine. But when I forced a specific pattern — ten quick 429s followed by success — our job retried, then recorded the failure as permanent because of a subtle bug in how we mapped 429 vs 500. The proxy showed the exact interleaving of events that caused the race and let me produce a failing test. We fixed the mapping, pushed a patch, and the next night production 429s stopped causing missed payouts.

The things the proxy didn’t catch (my honest failure) After the fix, I slept better. Until a week later, when the gateway started throttling us again. Same error class. Only this time the proxy tests suggested we were fine, but production still broke.

Root cause: the gateway’s rate limits weren’t per‑client key or per‑IP. They were global and influenced by a mix of merchant id and merchant subaccounts hitting the same quota on the vendor side. My proxy simulated per‑client token buckets keyed to the API key header — which made it a useful tool, but a poor model for that vendor’s global policy. I had traded speed for fidelity. I had a false sense of confidence.

How I patched the patch

Tradeoffs I accepted

Why this is particularly useful in India Payment flows in India (UPI, netbanking, card gateways) are noisy. A single merchant event (salary day, cashback promo on a payments app) can create correlated load. Mobile networks and slow bank callbacks make timing assumptions fragile. A cheap local proxy means you can reproduce a “gateway unhappy” story without burning API quotas, without waiting for bank batches, and without juggling multiple test bank accounts or costly per‑request fees.

Practical snippet (what I actually ran)

Takeaway A tiny, local rate‑limit proxy is cheap to build and gives you a powerful, repeatable way to test throttling and retry behavior. But don’t trust it blindly. Start simple, add modes that reflect how your vendor actually scopes rate limits, and pair it with at least one low‑cost real integration test. The proxy finds classes of bugs you won’t otherwise see — and it also makes you painfully aware of the limits of your models. That’s good. It keeps you debugging in production less often.