DEV Community

Cover image for Why a Sudden Burst of Bot Traffic Can Wreck Your n8n Webhook Bill (And How to Stop It)
John Dave Decano
John Dave Decano

Posted on

Why a Sudden Burst of Bot Traffic Can Wreck Your n8n Webhook Bill (And How to Stop It)

Most of the time your n8n chat webhook sees ordinary traffic: a handful of messages spread over a conversation. Then something sends it a burst. A bot scraping the endpoint, a scanner probing for open webhooks, or a spike in real visitors landing at once. Every one of those requests can trigger a paid API call downstream. If your workflow calls OpenAI on every message, a burst isn't a traffic spike, it's a bill spike.

That's the actual problem: nothing stands between "someone found my webhook URL" and "my workflow ran, and paid for, every request they sent."

Where the burst actually comes from

Once a webhook URL is reachable from the public internet, it's reachable by anything that can send an HTTP request, not only your widget. A forum user building a public-facing bot ran into this directly and asked "what you guys do to avoid potential DDOS and save credits". Bots and scanners don't need to know what your workflow does. They only need the URL, and a chat widget's webhook is trivially easy to find by anyone reading page source.

Legitimate traffic can burst too. Several visitors landing on a widget at the same moment, or one visitor double-clicking send because nothing told them the first click registered, produce the same shape of problem: more requests in a short window than your workflow, or your downstream API budget, was built to absorb.

Either way, the workflow has no way to tell a visitor sending a burst of retries from a script hammering the endpoint from twenty real people arriving at once. All three look like a pile of requests. All three get forwarded to whatever paid API sits behind the workflow, unless something filters before that point.

What sits between a burst and your API bill

Visitor / bot / script
        |
        v
  Chat widget (browser)
        |
        v
  Gateway  --- rate limit check (per IP + per session) --- BLOCK if over limit
        |
        v  (only requests that clear the limit)
   n8n webhook
        |
        v
  OpenAI / paid API call
Enter fullscreen mode Exit fullscreen mode

Without the gateway step, every arrow above collapses into one: browser straight to n8n straight to OpenAI. Nothing between "request arrives" and "API gets billed."

Why fixes at the workflow level don't stop it

The instinctive response is to handle this inside n8n, hand-roll a check, add a Wait node, or dedupe repeated payloads. That same forum thread landed on hand-rolling a Cloudflare WAF rule plus JS obfuscation on top of the widget. It's a real workaround, and also a sign the fix doesn't live inside the workflow: obfuscated JavaScript still runs in the browser, so a determined script can still extract the URL and call it directly, bypassing the widget (and any client-side logic) entirely.

Workflow-level settings, dedupe nodes, sequential execution, control how n8n processes a request once it's already been accepted. They don't control whether a request gets accepted in the first place. They also can't distinguish a burst worth serving from a burst worth blocking. By the time a request reaches the workflow, whatever it's going to cost you downstream is already committed.

The correct fix: throttle in front of the webhook, not inside it

If the goal is "cap what a burst can cost me," that has to happen before the request becomes a workflow execution or a call to OpenAI, at the layer that receives the request first. A gateway in front of the webhook doing rate limiting per IP and per session means a bot, a script, or an unusually bursty visitor hits a wall immediately, before anything downstream gets triggered.

What token-bucket rate limiting actually is

A token bucket is a simple counter per client (here, per IP and per session). It starts full and refills at a steady rate, say one token per second, up to some cap. Every request costs one token. Plenty of tokens left, the request goes through. Bucket empty, the request gets rejected until it refills.

The effect: ordinary chat behavior, a few messages spread over a conversation, never drains the bucket. A burst, scripted or accidental, drains it fast and gets throttled on the spot, before a workflow execution or an API call happens. That's a cleaner failure mode than an error surfacing downstream. The request never gets far enough to cost you anything, and your widget can show a normal "slow down" message instead of a broken chat.

Building that layer yourself

The core idea is a counter per IP or session, a refill rate, a check before the request gets forwarded. Where it gets harder is making the limiter itself hard to route around. Per-IP limiting alone is weak: a shared network or a session that reconnects on a new IP resets the count. Per-session limiting alone is weak too: a script can mint new sessions faster than you can block them. You need both, and the session side needs to be tied to something a script can't forge on demand, a signed token, not a plain cookie.

That's ongoing security surface to maintain, on top of the rest of the proxy work described in how to hide your n8n webhook URL from the browser. If you're already running that proxy, rate limiting belongs in the same layer.

How ChatFlowGate rate-limits requests before they reach n8n

This is the exact spot ChatFlowGate sits: in front of your n8n webhook, doing token-bucket rate limiting per IP and per session before a request ever reaches n8n or triggers a downstream API call. A bot hammering the endpoint, or a scanner that found the URL, gets throttled at the gateway, not billed to your OpenAI account.

Sessions are HMAC-SHA256-signed and bot-bound with a 24-hour expiry, so the per-session side of the limiter is tied to something a script can't mint fresh copies of to dodge the count. Origin validation and domain allowlisting stop the endpoint from being called from outside your widget in the first place. IP banning, country allow/block lists, and spam traps handle abuse patterns beyond ordinary bursts. None of it touches your n8n workflow: it still receives one webhook call per message that clears the limiter, just from the gateway instead of directly from whatever sent the burst.

To set it up:

  1. Point your bot's webhook in ChatFlowGate at your existing n8n webhook URL.
  2. Rate limits apply automatically, per IP and per session, no config on the n8n side.
  3. Drop in the one-script embed. Bursts get throttled at the gateway; your downstream API calls only fire for requests that clear it.
  4. Any dedupe or throttling logic you already have inside the workflow still works fine underneath this, there's just far less burst traffic ever reaching it.

Free tier covers 500 messages a month on one bot, enough to point a real chat widget at it and watch the limiter hold under an actual burst before committing to anything.

FAQ

How is this different from a WAF rule in front of n8n?
A general WAF rule doesn't know about chat sessions. It can rate-limit by IP but has no concept of "this visitor already has a valid session." Per-session limiting means legitimate visitors on a shared IP (office wifi, VPN) aren't throttled alongside an actual bot.

Does this stop scrapers who already have the webhook URL?
If they're calling ChatFlowGate's gateway URL instead of n8n's, yes, the limiter applies to every request regardless of where it came from. It's also why the webhook URL being hidden from the browser in the first place matters, see the linked post above.

Will legitimate traffic spikes get throttled too?
Ordinary bursts, several real visitors arriving close together, stay well inside a token bucket's refill rate. The limiter is tuned to catch sustained or scripted bursts, not normal variance in visitor timing.

What if my traffic is low and bursts aren't a problem yet?
Then this isn't urgent. It becomes urgent the moment a bot finds the URL, at which point it's a five-minute setup instead of an unexpected bill.

If you're not sure what happens to your API spend the first time a bot finds your chat widget's webhook, that's worth checking before it happens for real. chatflowgate.com has the free tier if you want to see the limiter behave against a live workflow.

Top comments (0)