What is P99 latency?
P99 latency is the 99th percentile of response times. Sort every request in a time window by duration, and the p99 is the value that 99% of them are faster than. If your webhook handler's p99 is 800 ms, one request in a hundred takes longer than 800 ms. Its siblings are p50 (the median, the typical request), p95 and p99.9; together they describe the tail of the distribution, which is where users and upstream systems feel slowness. Latency is the first of the Four Golden Signals, and percentiles are how it should be reported.
Averages hide that tail. Latency distributions are skewed: most requests are quick, and a few (a cold cache, a lock, a garbage-collection pause, a retry inside a dependency) are very slow. A handler with a 120 ms average can still have a 3-second p99, and at 600 status webhooks a second that is six slow requests every second. The tail also compounds: a request that fans out to 100 backend calls, each with a 1% chance of being slow, hits at least one slow call about 63% of the time (1 − 0.99¹⁰⁰).
Percentiles must be computed from the underlying distribution, usually a histogram; you cannot average the p99s of five servers to get the fleet's p99. P99 latency measures how fast individual requests are, while Throughput measures how many complete per second, and the two trade off as a system nears saturation. A Request Timeout should sit above the p99 you expect, and a Service Level Indicator is often written as a percentile, such as "99% of OTP sends accepted within 2 seconds".
Key takeaways
- P99 latency is the time that 99% of requests beat: one request in a hundred takes longer.
- Averages hide the tail. A 140 ms mean can sit on top of a p99 of six seconds.
- Compute percentiles from merged histograms; averaging the p99s of several servers gives a wrong answer.
- Fan-out multiplies the tail: 100 calls with a 1% chance of being slow hit at least one slow call 63% of the time.
- Set SLOs, alerts and timeouts on p95 and p99, not on the mean.
Figure 1
Where does the slowness hide? Compare the tail
- Synchronous CRM call
- Acknowledge, then queue
Grouped · ms (illustrative)
Highest bar: Synchronous CRM call in p99.9 at 9,400 ms (illustrative).
Hover, focus or tap a column for exact values; press it to pin.
| Category | Synchronous CRM call | Acknowledge, then queue |
|---|---|---|
| Mean | 140 | 38 |
| p50 | 50 | 30 |
| p90 | 110 | 55 |
| p95 | 160 | 80 |
| p99 | 6,100 | 180 |
| p99.9 | 9,400 | 420 |
Example: A healthy average, a broken tail
A Jaipur jewellery retailer's webhook dashboard shows a comfortable 140 ms average acknowledgement time, yet Meta keeps re-delivering status events after every campaign. Plotting percentiles reveals a p99 of 6.1 seconds: about one webhook in a hundred waits on a synchronous CRM update that sometimes stalls. Moving the CRM call behind a queue, so the handler only verifies the signature, queues the event and returns 200, brings the p99 down to 180 ms, and the duplicate statuses stop.
Why it matters
Why does P99 latency matter?
Customers never experience your average. The person whose OTP arrives in 30 seconds instead of two gives up on the login, and the webhook that takes eight seconds to acknowledge is the one Meta may treat as failed and retry, creating duplicates and a backlog. Tracking p95 and p99 for Graph API calls, end-to-end send time and Webhook Acknowledgement catches these problems while the average still looks healthy. It is also the right basis for SLOs, timeouts and capacity planning.
Common mistakes with P99 latency
Averaging percentiles
The mean of five servers' p99s is not the fleet's p99, and neither is a daily average of five-minute p99s. Merge the histogram buckets first, then compute the percentile.
Reporting the mean
An average dominated by fast requests hides the slow ones customers notice. Put p50, p95 and p99 on the same chart.
Trusting only the load generator's numbers
In Load Testing, a closed-loop tester that waits for each response sends less traffic as the system slows, so it under-reports the tail. Use arrival-rate load and measure on the server as well.
Buckets that miss the target
If your histogram jumps from 1 s straight to 10 s, a p99 of 2 s cannot be seen. Place buckets densely around your SLO thresholds.
Too few samples
A p99 over a quiet five minutes may rest on one or two requests and swing wildly. Lengthen the window, or use p95 for low-traffic paths.
How does P99 latency work?
Record every request in a histogram
Measure Graph API calls, queue wait, end-to-end sends and webhook handling as histograms, with bucket boundaries around your targets (for example 25 ms to 10 s).
Compute percentiles over a window
Query p50, p95 and p99 over a sliding window such as five minutes, adding the bucket counts up across instances before you take the percentile.
Check the sample size
A p99 needs enough data: with 100 requests it rests on the slowest one or two. Use longer windows, or track p95, for low-traffic endpoints.
Find what lives in the tail
Use Distributed Tracing to pull the requests slower than the p99 and look for the common factor: a slow dependency, a large payload, a cold instance, a lock.
Set targets and alerts on the tail
Write SLOs and alerts on p95 or p99 rather than the mean, and set client timeouts above the expected p99 so normal slow requests are not cut off.
P99 latency in code
import client from "prom-client";// Buckets in seconds, dense around the targets you care about.const ackDuration = new client.Histogram({name: "webhook_ack_duration_seconds",help: "Time from webhook received to 200 sent",buckets: [0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],});app.post("/webhooks/whatsapp", (req, res) => {const end = ackDuration.startTimer();verifySignature(req); // X-Hub-Signature-256 over the raw bodyqueue.add("event", req.body); // the slow work happens laterres.sendStatus(200);end();});
Record durations as a histogram, not a pre-computed percentile, so you can aggregate across instances later.
Best practices for P99 latency
- Track p99 separately for OTP, transactional and marketing sends, because their targets differ.
- Measure end-to-end send time (queued to
wamidreturned) as well as the Graph API call on its own. - Keep traces of requests slower than the p99, so you can explain the tail and not just measure it.
- Set timeouts from the measured p99 plus a margin, and revisit them after big changes.
P99 Latency FAQs
- What does p99 latency mean?
- It is the time within which 99% of requests finish; the slowest 1% take longer. A p99 of 500 ms means one request in a hundred takes more than half a second.
- Why use p99 instead of average latency?
- Because averages hide slow requests. Latency is skewed, so a healthy-looking mean can sit on top of a long tail that real users, timeouts and retry logic all run into.
- What is the difference between p95 and p99 latency?
- p95 is the time 95% of requests beat; p99 is the time 99% beat. p99 looks further into the tail and needs more data to be stable, which is why low-traffic endpoints often track p95 instead.
- What is the difference between latency and throughput?
- Latency is how long one request takes; throughput is how many requests complete per second. As a system approaches saturation, throughput flattens and tail latency climbs steeply.