🛡️ Portarus: the gateway for AI agents Discover more →
Engineering

How to Stop Duplicate Messages to Your AI Agent

Stop paying for the same message twice.

Orlando Quijada · · 6 min read
Three identical 'order #123' messages hitting the Portarus gateway, two stamped as duplicates and rejected so only one reaches the AI agent
  • 18% of the messages coming in are repeats — up to 3× on a busy day.
  • Repeats are part of the ~43% total input waste you pay to process.
  • Waste drops to under 1% once you put a filter in front.

The problem

  • Webhooks are delivered at least once — the same event arrives two or three times.
  • Your agent has no memory that it already ran, so it fires again for every copy.
  • You pay the full token cost on each duplicate run.

The solution

Catch the copies before the agent runs.

Give each message a fingerprint — who sent it + what they said, cleaned up — and remember it for a little while. If the same fingerprint shows up again, skip it.

  1. Fingerprint = sender + message (trim spaces, ignore timestamps).
  2. Remember it for a short window — say 30–120 seconds.
  3. Seen it already? Skip it. New? Let it through.

Doing something real like a payment or order? Tag it with a one-time key so a repeat can't act twice.

Result: one request → one agent run, however many copies arrive.

See the full fix
A closer look

Your AI agent ran three times last night for one user request, and you paid for all three. That is not a bug in your model or your prompt — it is duplicate messages, the most common and most invisible waste in a webhook-driven pipeline. Here is why it happens and how to stop it.

Why duplicate messages reach your AI agent

Webhooks are delivered at least once by design. Messaging platforms, payment providers, and automation tools all retry delivery when they do not receive a fast enough acknowledgement — so the same event routinely arrives two or three times. Your agent has no memory of the previous copy; it treats each delivery as a fresh request and runs again.

The result is silent multiplication. One intent becomes N executions, N model calls, and sometimes N duplicate replies to a confused user. Because each copy looks legitimate in isolation, the waste never shows up as an error — only as a bill that is higher than your actual traffic.

There are two ways to stop it: build the defenses yourself, or put a gateway in front that does it for you. This post walks the full manual path first — so you understand exactly what’s involved — then lets you compare the two at the end.

The three sources of duplicates

Not all duplicates are the same, and knowing the source tells you which defense you need.

SourceWhat causes itHow you catch it
Delivery retriesThe platform re-sends after a timeoutSame message ID
User repeatsAn impatient user sends againSame fingerprint
Integration loopsA misconfigured fan-outSame fingerprint + window

Delivery retries carry the same message ID, so they’re the easiest to catch. User repeats and integration loops come with fresh IDs but identical content — so you catch those by their fingerprint, not the ID.

The manual fix (Option 1)

Three defenses, layered. Each one is something you build and maintain yourself.

Fix 1: give each message a fingerprint

Make a little “fingerprint” for every message: take who sent it plus what they said, clean it up, and turn that into a short code. Keep a list of the codes you’ve already handled. When a new message makes a code you’ve seen, drop it. This catches both platform retries and users hammering send, because it looks at what was said — not at a fragile platform ID.

On a gateway, the fingerprint and the list are built in — nothing to set up.

Fix 2: only remember fingerprints for a short while

The fingerprint alone isn’t enough — you need a time limit. “Book me a table” sent now and again tomorrow are two real requests, not a copy. So only treat a message as a duplicate if its twin showed up recently: seconds to a few minutes, depending on your traffic.

Match the window to how your users behave. Too short and retries a few seconds apart slip through; too long and you block people who genuinely ask again. A window of 30–120 seconds catches almost every retry storm without eating real requests.

On a gateway, the window is just a setting you pick — not code you write.

Fix 3: for real actions, add a one-time key

If a message does something — a booking, a payment, an order — add a one-time key to it so that if it arrives twice, it still only happens once (instead of charging the card twice). This is the same trick Stripe uses for safe retries: the request carries a unique key, and the server makes sure the action runs a single time, no matter how many copies land. It’s your safety net for the rare duplicate that slips past the window.

On a gateway, this one-time safety is handled for you — no extra wiring per action.

The catch: you have to keep it running

None of this is one-and-done. You host the fingerprint store, tune the windows as traffic changes, and wire idempotency into every side-effect — then repeat the whole setup on every channel you add, and patch it when platforms change their retry behavior. For a single workflow it’s fine. Across a fleet of channels, the maintenance quietly becomes its own job.

Place a gateway layer (Option 2) — faster

Instead of building the three defenses into every workflow, you point your channels at one layer that already does all of it — hashing, windows and idempotency — before anything reaches your agent.

  • One layer, every channel. Set it up once; it covers WhatsApp, Slack, webhooks and the rest.
  • Nothing to maintain. No fingerprint store to host, no retry quirks to patch.
  • Tune, don’t code. The window is a setting, not a code path.
  • Minutes, not hours. Your agent only ever sees the first, clean copy.

You trade a little control of the internals for a lot less work. For most teams running more than one channel, that’s the trade worth making.

Option 1 (DIY) vs Option 2 (gateway)

Neither is wrong — one trades time for control, the other control for time. The honest side-by-side:

 Do it yourselfUse a gateway
Setup time ✕Hours per channel ✓Minutes
Hash store & windows ✕You build & host ✓Built in
Idempotency keys ~Wire per side-effect ✓Built in
Every channel ~Repeat setup each time ✓One layer, all channels
Ongoing maintenance ✕Yours, forever ✓Handled for you
Control of internals ✓Full ~Abstracted away

If option 2 sounds more like you, here’s the short version of how to set it up: Install Portarus and save time and complexity →.

Either way — go make your agent run once per request.

Thanks for reading. 👋

Orlando. -

Found this helpful? Share it with someone who needs it

OQ

Orlando Quijada

Technologist with 15+ years of experience. Developer, SaaS tester, and AI collaboration & optimization specialist. Currently contributing at portarus.com.

Written on

What to read next