The most common integration we see in the logs looks like this: the client creates an order and starts polling its status in a loop, once a second, until a code shows up. It works, but the whole system pays for it — and the client pays first.
The problem with polling isn't server load — it's latency and rate limits. There's always a gap between the code appearing and you actually picking it up, bounded by the next request. To shrink that gap, a developer shortens the interval, hits the rate limit, gets a 429, and bolts on retries with backoff. The integration gets more complicated, and the code doesn't arrive any faster.
A webhook flips the model: you give us a handler URL, and we send a POST request the moment the message is parsed. No loop, no waiting for the next iteration — average latency drops to a few hundred milliseconds. The handler receives the order ID, the number, the code, and the full message text.
Write the handler to be idempotent. We guarantee at-least-once delivery, which means the same code can arrive twice if there's a network hiccup. Simple rule: treat the order ID as the key, silently ignore a repeat delivery with the same ID, and always respond 200 once you've accepted the event. If you return an error, we'll retry five times with a growing interval — from ten seconds up to five minutes.
Verify the signature before you parse the body. Every request carries a header with an HMAC signature computed over the raw body and your secret. Compare signatures using a constant-time function, and only then parse the JSON — otherwise the handler becomes an open endpoint anyone can post fake codes to.
Polling doesn't disappear entirely — it's still useful as a safety net. A sensible setup is webhooks as the primary channel plus an occasional background sweep over orders that have been waiting longer than usual. That covers both speed and the case where your handler was down longer than the retry window.
Have a question about this post? Write to us.
All materials