Support automation

Polling vs Webhooks for a Chat-Support API: Why We Chose Polling

Our support bot polls the chat API every few minutes instead of using webhooks. Why that is more reliable, and the chats a webhook would never have shown us.

· by OI

Every integration guide says the same thing: use webhooks, polling is for people who haven’t read the guide. I read the guide. We poll anyway. Here’s why, with the one incident that settled it.

Polling vs webhooks

The bot knocking on the chat API door asking if anything closed

What the bot needs

The claims bot needs to know when a support chat closes, so it can summarise it and write a row. That’s it. It needs that fact within, say, an hour. It runs its batch at 03:00, so “within a few minutes” is already luxury.

A webhook gives you that fact instantly, once, if your server is up, if the platform retries properly, if nobody rotated the secret, if the request didn’t get eaten by a proxy. Polling gives you that fact every few minutes, forever, as long as your loop runs.

Why we poll

The three boring reasons

1. No public endpoint. A webhook means a URL on the internet that accepts POSTs. Our bots run on a Mac in an office. Exposing that machine means a tunnel, a reverse proxy, a certificate, and a new category of thing that can break at 2am. Polling means the Mac makes outbound calls, like a normal computer, and nothing on the internet knows it exists.

2. Restarts are free. The poll loop keeps two things: a cursor (last_closed_at) and a set of chat ids it’s already handled. Crash anywhere, restart, and the next loop picks up from the cursor and skips the done ids. No missed events, no duplicate rows. With a webhook, every event that arrived while you were down is a question you have to go ask the platform, and some platforms don’t answer.

The poll loop, in spirit

3. Retries cost nothing. The platform’s API has a bad five minutes? The poll fails, logs one line, tries again next time. With a webhook, the platform decides how many times to retry and for how long, and the answer is usually “three, then never”.

The incident that settled it

A messaging workflow on the platform (one of those “automatically greet the customer” flows) had a bug: for some customers it left the chat in an initial state. Not open. Not closed. Initial. A state the list endpoint doesn’t return for either filter. Those chats were invisible to the list API and, as far as any webhook was concerned, nothing had ever happened to them.

Customers were waiting. Nobody could see them. The chats existed only if you knew the id and fetched them directly.

The chats the list API never showed

We found them because polling made us look at the API often enough to notice a count that didn’t add up. The fix on our side was a small hourly check, stuck.py: fetch recent chat ids by a different route, flag anything older than a few minutes that’s still initial, post an alert. Alert only. It doesn’t try to un-stick anything, because the stuck state is the platform’s bug and poking it from a bot is how you get two bugs.

A webhook system would have been perfectly healthy throughout. All its events would have fired correctly. The chats it never heard about would still be sitting there.

When webhooks win

If the bot had to respond to a customer within seconds, polling every few minutes would be useless and I’d be writing the opposite post. If the bots lived on a server with a public address anyway, the endpoint objection goes away. If the platform offers a replay endpoint for missed events, the restart objection mostly goes away too.

None of those are true for us. Our job is “summarise it sometime tonight”, and for that job, polling is not the lazy option. It’s the one that survives a laptop lid.

What we’d tell you

  1. Decide the latency you actually need before reading anyone’s architecture advice, including this.
  2. If you poll, keep a cursor and a done-set. That’s the entire reliability story.
  3. Count what the API returns and compare it to what you expect. The things it doesn’t return are where the incidents live.

One line from me

None of this is hard. It’s the tedious, soul-sucking grunt work nobody wants to do. Hand every bit of it to the bot.