AdieraBot: The Architecture Behind My WhatsApp Fact-Checking Agent

Misinformation on WhatsApp is personal for me. I see it firsthand with my dad, and that is what got me building AdieraBot: a WhatsApp agent you can forward a claim to and get back a verdict, a reason, and something to check next.

Earlier this year I wrote a tutorial for the Twilio blog that walks through building a version of this bot with Twilio, OpenAI, and Node.js. If you want to build one yourself, start there. The tutorial version is deliberately simple: every message that arrives is sent to an LLM with web search enabled, and the answer comes back to the user.

That is the right way to learn the moving parts. It is also the wrong way to run the bot for real, because it pays full price for every single message. This post is about the production architecture, which I also presented at the Twilio Builder Community Meetup in Manchester.

The problem with checking everything from scratch

Misinformation is not original. The whole reason it is dangerous is that the same claims get forwarded again and again. If a false story about a health cure is doing the rounds, the bot will receive that claim hundreds of times in slightly different packaging.

An LLM call with web search is the most expensive and slowest thing the bot can do. It takes several seconds and costs real money per call. Doing that work repeatedly for a claim we have already verified is waste, and at WhatsApp scale the waste compounds quickly.

So the production bot treats the LLM as the last resort, not the front door. Claims pass through three tiers, ordered from cheapest to most expensive, and each tier only sees the traffic the tier before it could not handle.

Tier 1: the exact-match cache

When a text claim arrives, it is normalised (lowercased, punctuation and extra whitespace stripped) and looked up against a PostgreSQL table of claims we already have verdicts for.

Two things make this tier better than it sounds:

  1. Forwarded messages are copies. WhatsApp misinformation mostly spreads by forwarding, and a forward is byte-for-byte identical to the original. Exact matching catches far more than you would expect.
  2. The database was not born empty. I seeded it from the Google Fact Check Tools API, which aggregates published fact-checks from organisations around the world. From day one, the bot could answer common viral claims without ever calling a model.

A tier 1 hit costs nothing and returns in milliseconds.

Tier 2: semantic similarity

The same claim also travels with small mutations: someone retypes it, translates it loosely, or adds their own commentary. Exact matching misses these, but they are still the same claim.

Tier 2 embeds the incoming claim and runs a vector similarity search against the claims we have already verified. If the best match scores above a threshold, we reuse the stored verdict instead of re-verifying.

The cost here is one embedding call, which is orders of magnitude cheaper than generation with web search. The main design risk is a false positive, where two claims are similar in wording but different in substance, so the threshold is set conservatively. When in doubt, fall through to tier 3.

Tier 3: the LLM with web search

Only genuinely new claims reach the top tier. Here the bot uses OpenAI's Responses API with the built-in web search tool, so verification can consider current reporting rather than just training data.

The response is forced into a fixed template: Verdict, Reason, Check next, and Confidence (low, medium, or high). The template matters more than it looks. A fact-checker that sounds breezily confident about everything is worse than no fact-checker, so the instructions make clear that "Unclear" is a perfectly valid answer.

The important part is what happens after: the verdict is written back to the database. The next person who forwards the same rumour hits tier 1 or tier 2. Every expensive call makes all future calls cheaper. The pipeline is a flywheel, not a filter.

Images, audio, and video

Plenty of misinformation arrives as a screenshot or a voice note rather than text. Media does not skip the pipeline, it just enters through a different door:

  • Images go to a multimodal model directly. No separate OCR step; the model reads the screenshot and extracts the claim.
  • Audio and video are transcribed first (I use gpt-4o-mini-transcribe), and the transcript then flows through the same three tiers as a text message.

Twilio stores inbound media at authenticated URLs, so the media service downloads with Basic Auth before anything else happens. The tutorial covers this part in detail.

Rate limiting, because WhatsApp costs money twice

Every conversation costs money on the Twilio side, and every tier 3 miss costs money on the OpenAI side. Without limits, one enthusiastic user or one script pointed at the number could drain the budget in an afternoon.

The bot keeps simple per-user counters and enforces a daily cap. Users who hit the cap get a friendly message rather than silence. It is not sophisticated, and it does not need to be; it just needs to make the worst case boring.

What I would tell you to steal

If you take one thing from AdieraBot, take the shape, not the code: put your cheapest checks in front, let each tier absorb what it can, and write expensive results back so they become cheap results tomorrow. The same shape shows up in a retrieval assistant I built, which I have written about in Hybrid Retrieval: Not Every Question Needs an LLM Call.

And if you want the build-along version with all the code, the Twilio tutorial is the place to start. If you build one for your own family group chats, I would genuinely love to hear about it.