3 seconds to 200ms, and none of the slow code got any faster. What changed was what the endpoint promised.
The endpoint returned payment status. Status meant "whatever the provider says right now", so every read made an outbound call to the provider, sometimes two, when the first came back in a transitional state. Most of those three seconds was somebody else's network, sitting inside my request.
You cannot optimize a remote call you are required to make, so I stopped requiring it. The provider is no longer the source of truth for reads: it is a source of events. Webhooks plus a reconciliation poller write into a status projection I own, and the endpoint reads that. 200ms is a local indexed read.
The honest part: the latency did not disappear. It moved out of the request and into a staleness window. That is a weaker guarantee, so I state it rather than hide it. The response carries an as_of timestamp and a freshness bound, records the poller could not confirm get flagged instead of being served as if they were fresh, and the one caller that genuinely needs live state gets a separate slow path that says so in its name.
Most 3s-to-200ms stories get told as tuning wins. Nearly every one I have shipped was a contract change: the endpoint stopped answering a harder question than the caller had actually asked.
I wrote up the longer version (projection design, the reconciliation loop, how to choose the freshness bound) on my blog: https://polycratia.com/c/40af4afb
When you cut the latency of a read path, the staleness lands somewhere. Worth checking whether the caller can see it...