Three seconds on an API call usually isn't a slow handler. The request waited for a worker before any of your code ran.
The mechanics are dull. A synchronous worker pool has fixed concurrency. Every request holds one worker for its whole duration, including the time it sits blocked on a database connection or an outbound HTTP call. A blocked worker is still a busy worker. Once arrival rate times average hold time crosses the pool size, new requests queue at the socket, and queue wait doesn't grow linearly: it goes off a cliff.
Which is also why the profile lies to you. The span inside the view starts when the request is dequeued, so your tracing reports milliseconds while the client measures seconds. That missing time never got attributed to any function, because no function was running.
The second-order effect is the one that actually bites: the endpoint you're profiling is often not the endpoint causing the latency. One report export holding workers for seconds will make every payment callback on the same pool slow. The callback isn't broken. It just has bad neighbours.
So I measure accept-to-handler wait as a first-class metric, not just handler duration. I isolate endpoints with external dependencies onto their own workers, cap concurrency per dependency so one slow upstream can't eat the whole pool, and keep the money path (callbacks, settlement, anything that must not retry blindly) on a pool nothing else shares.
Latency is a property of the pool. The function just takes the blame for it.
Worth checking which of your critical endpoints share a worker pool with your slowest ones right now...