<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Dave Decano</title>
    <description>The latest articles on DEV Community by John Dave Decano (@johndavedecano).</description>
    <link>https://dev.to/johndavedecano</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F686340%2F965803fa-a1a0-4f30-b7a3-71d2c3456fa8.jpeg</url>
      <title>DEV Community: John Dave Decano</title>
      <link>https://dev.to/johndavedecano</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johndavedecano"/>
    <language>en</language>
    <item>
      <title>Why a Sudden Burst of Bot Traffic Can Wreck Your n8n Webhook Bill (And How to Stop It)</title>
      <dc:creator>John Dave Decano</dc:creator>
      <pubDate>Thu, 30 Jul 2026 18:09:26 +0000</pubDate>
      <link>https://dev.to/johndavedecano/why-a-sudden-burst-of-bot-traffic-can-wreck-your-n8n-webhook-bill-and-how-to-stop-it-19hf</link>
      <guid>https://dev.to/johndavedecano/why-a-sudden-burst-of-bot-traffic-can-wreck-your-n8n-webhook-bill-and-how-to-stop-it-19hf</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Where the burst actually comes from
&lt;/h2&gt;

&lt;p&gt;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 &lt;a href="https://community.n8n.io/t/how-to-properly-secure-the-webhook-if-must-be-exposed/100102" rel="noopener noreferrer"&gt;"what you guys do to avoid potential DDOS and save credits"&lt;/a&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  What sits between a burst and your API bill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fixes at the workflow level don't stop it
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The correct fix: throttle in front of the webhook, not inside it
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  What token-bucket rate limiting actually is
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building that layer yourself
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That's ongoing security surface to maintain, on top of the rest of the proxy work described in &lt;a href="https://dev.to/blog/hide-n8n-webhook-url-from-browser"&gt;how to hide your n8n webhook URL from the browser&lt;/a&gt;. If you're already running that proxy, rate limiting belongs in the same layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How ChatFlowGate rate-limits requests before they reach n8n
&lt;/h2&gt;

&lt;p&gt;This is the exact spot &lt;a href="https://chatflowgate.com" rel="noopener noreferrer"&gt;ChatFlowGate&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To set it up:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Point your bot's webhook in ChatFlowGate at your existing n8n webhook URL.&lt;/li&gt;
&lt;li&gt;Rate limits apply automatically, per IP and per session, no config on the n8n side.&lt;/li&gt;
&lt;li&gt;Drop in the one-script embed. Bursts get throttled at the gateway; your downstream API calls only fire for requests that clear it.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How is this different from a WAF rule in front of n8n?&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this stop scrapers who already have the webhook URL?&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will legitimate traffic spikes get throttled too?&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if my traffic is low and bursts aren't a problem yet?&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;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. &lt;a href="https://chatflowgate.com" rel="noopener noreferrer"&gt;chatflowgate.com&lt;/a&gt; has the free tier if you want to see the limiter behave against a live workflow.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>automation</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>How to Hide Your n8n Webhook URL From the Browser (Without Building Your Own Backend)</title>
      <dc:creator>John Dave Decano</dc:creator>
      <pubDate>Thu, 30 Jul 2026 17:43:14 +0000</pubDate>
      <link>https://dev.to/johndavedecano/how-to-hide-your-n8n-webhook-url-from-the-browser-without-building-your-own-backend-2nl9</link>
      <guid>https://dev.to/johndavedecano/how-to-hide-your-n8n-webhook-url-from-the-browser-without-building-your-own-backend-2nl9</guid>
      <description>&lt;p&gt;Open dev tools on almost any n8n Chat Trigger widget and you'll find the webhook URL sitting in plain text, either in the page source or in the first XHR request the widget fires. Anyone who views source gets a live URL that talks directly to your n8n instance.&lt;/p&gt;

&lt;p&gt;That's the actual problem. Not "is n8n secure," but "why does my chat widget hand out the address of my automation server to every visitor, and what do I do about it."&lt;/p&gt;

&lt;h2&gt;
  
  
  What "exposing a webhook URL" actually means
&lt;/h2&gt;

&lt;p&gt;An n8n webhook node generates a URL like &lt;code&gt;https://your-instance.app.n8n.cloud/webhook/abc123&lt;/code&gt;. That URL is the entire authentication model. Anyone who has it can POST to it directly, no login, no API key, nothing standing between them and your workflow.&lt;/p&gt;

&lt;p&gt;The n8n Chat Trigger widget is designed to call that URL straight from the browser. That means the URL has to ship in client-side JavaScript. There's no version of "embed a chat widget that calls n8n directly" where the URL doesn't end up in the browser, because the browser is the thing making the request.&lt;/p&gt;

&lt;p&gt;Once it's out, three things follow. People scrape it and call your webhook from scripts, curl, or Postman, outside your widget entirely. Your workflow runs on whatever traffic shows up, and any spend behind it (OpenAI calls, paid APIs) runs with it. And if the workflow does anything sensitive, the webhook URL is the only thing standing between "chat widget" and "unauthenticated API into my internal automation."&lt;/p&gt;

&lt;p&gt;This isn't a hypothetical. A &lt;a href="https://community.n8n.io/t/n8n-cloud-cors-issue-with-embedded-chat-despite-setting-allowed-origins/270918" rel="noopener noreferrer"&gt;CORS thread on n8n's own forum&lt;/a&gt; about the Chat Trigger embed is one of more than ten separate threads on the same failure mode since 2023. People trying to lock the widget down to their own domain keep hitting it, which tells you the "just restrict origins" answer isn't holding up in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CORS and origin checks don't hide anything
&lt;/h2&gt;

&lt;p&gt;The instinctive fix is to lock down allowed origins in n8n's webhook settings, or drop a Cloudflare rule in front of it. Worth doing. Doesn't hide the URL.&lt;/p&gt;

&lt;p&gt;CORS is a browser-enforced policy that stops &lt;em&gt;browsers&lt;/em&gt; from reading cross-origin responses. It does nothing to stop a request sent with curl, a Python script, or Postman, none of which are browsers and none of which care what your &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header says. The URL is still sitting in your page source either way.&lt;/p&gt;

&lt;p&gt;One n8n user building a public-facing chatbot asked exactly this on the forum: &lt;a href="https://community.n8n.io/t/how-to-properly-secure-the-webhook-if-must-be-exposed/100102" rel="noopener noreferrer"&gt;"what you guys do to avoid potential DDOS and save credits"&lt;/a&gt;. The answer they landed on was hand-rolling a Cloudflare WAF rule plus JS obfuscation on top of it. Obfuscated JavaScript still runs in the browser, so the URL is still extractable, just with extra steps. That's a real workaround, and also a sign the actual fix (never sending the URL to the browser in the first place) doesn't exist yet in n8n itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The correct fix: don't let the browser see the URL at all
&lt;/h2&gt;

&lt;p&gt;If the webhook URL should never be scraped, the browser should never receive it. That means putting something between the chat widget and n8n: a proxy that holds the real webhook URL server-side, and gives the browser a different address to talk to.&lt;/p&gt;

&lt;p&gt;The browser calls your proxy. Your proxy calls n8n. The webhook URL never crosses the network boundary into client-side code, so there's nothing in page source or dev tools to copy.&lt;/p&gt;

&lt;p&gt;This is also where the rate-limiting problem gets solved for free, because you now have a place to put it. n8n's own core doesn't give you fine-grained control here, which shows up constantly on the forum: one thread asks for &lt;a href="https://community.n8n.io/t/env-for-rate-limit/96850" rel="noopener noreferrer"&gt;an env variable to raise n8n's hardcoded 5-concurrent-webhook-call limit&lt;/a&gt;, another describes &lt;a href="https://community.n8n.io/t/shopify-webhook-openai-http-request-causing-429-rate-limit-errors-multiple-executions-per-single-update/263562" rel="noopener noreferrer"&gt;a single Shopify product update firing multiple parallel executions&lt;/a&gt; that burst-call OpenAI and eat 429s. A proxy layer is the natural place to dedupe, throttle, and queue before any of that reaches n8n.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building that proxy yourself
&lt;/h2&gt;

&lt;p&gt;Nothing here is exotic. A minimal version is a few lines: a Cloudflare Worker or small Express app with one route, forward the POST body, add the real webhook URL from an environment variable, return the response.&lt;/p&gt;

&lt;p&gt;The gap is everything past "it forwards requests." A proxy that's actually safe to point at the internet needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Origin validation and domain allowlisting&lt;/strong&gt;, so only your embedded widget can call it, not any random site that finds the proxy URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting per IP and per session&lt;/strong&gt;, or you've just moved the DDoS problem one layer over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signed sessions&lt;/strong&gt;, so a client can't replay someone else's session or spoof one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSRF protection&lt;/strong&gt;, since a proxy that accepts a target and forwards it is a classic SSRF vector if it's ever made configurable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Somewhere to run it, and someone to patch it.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is hard individually. Together, it's a small backend service you now own: infra, monitoring, and security patching included. If you're running this for one internal workflow, that might be a reasonable Friday afternoon. If you're an agency running this setup for fifteen clients, you're maintaining fifteen copies of it, or one shared instance you now have to make multi-tenant.&lt;/p&gt;

&lt;p&gt;That second case comes up a lot. Agencies ask n8n directly how to white-label and manage multiple clients from one setup, and the answer from n8n's own team is usually a pointer to the enterprise &lt;strong&gt;Embedded&lt;/strong&gt; license just to get a per-client dashboard: &lt;a href="https://community.n8n.io/t/white-label-and-multiple-clients/181351" rel="noopener noreferrer"&gt;"this sounds like you might need our Embedded license. Please contact our Sales team"&lt;/a&gt;. Another agency owner asked if a single Pro plan could serve multiple clients and was told &lt;a href="https://community.n8n.io/t/can-i-use-my-pro-plan-to-deliver-automations-to-multiple-clients-or-each-one-needs-to-have-their-own-accounts/109866" rel="noopener noreferrer"&gt;"your clients will need separate instances"&lt;/a&gt;. If the proxy needs to serve more than one client, roll-your-own stops being a Friday afternoon and starts being a product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4o7r2tr3n15q9r1wsuvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4o7r2tr3n15q9r1wsuvg.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a hosted gateway instead
&lt;/h2&gt;

&lt;p&gt;This is the exact gap &lt;a href="https://chatflowgate.com" rel="noopener noreferrer"&gt;ChatFlowGate&lt;/a&gt; is built for: a gateway that sits between your chat widget and your n8n webhook so the browser never sees the real URL, without you standing up and patching your own proxy.&lt;/p&gt;

&lt;p&gt;The webhook URL is stored server-side and never sent to the client. Origin validation and domain allowlisting are built in. Requests get HMAC-SHA256-signed, bot-bound sessions with a 24-hour expiry, so a scraped session token isn't a standing key to your workflow. Rate limiting runs as a token bucket per IP and per session, so a Shopify-style burst or a scraper hitting the endpoint gets throttled before it reaches n8n. SSRF protection includes DNS resolution checks. IP banning, country allow/block lists, and spam traps handle abuse at the edge. And no chat content gets stored, only metadata, so you're not adding a new place for conversation data to leak.&lt;/p&gt;

&lt;p&gt;For agencies, the workspace model handles the case n8n's own team keeps redirecting to their enterprise license: one white-labeled widget per client (colors, logo, greeting, RTL), run from shared multi-tenant infrastructure instead of a separate n8n instance or a paid-for Embedded license per client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To set it up:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a bot in ChatFlowGate and paste in your existing n8n webhook URL. It's stored server-side, never returned to the browser.&lt;/li&gt;
&lt;li&gt;Set your allowed origins to the domain(s) the widget will run on.&lt;/li&gt;
&lt;li&gt;Drop in the one-script embed. It talks to ChatFlowGate's gateway, not your n8n instance.&lt;/li&gt;
&lt;li&gt;Your n8n workflow doesn't change. It still receives a webhook call, just from the gateway instead of directly from a browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Free tier covers 500 messages a month on one bot, enough to try it against a real workflow before deciding anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does hiding the webhook URL replace n8n's own security settings?&lt;/strong&gt;&lt;br&gt;
No. Keep IP allowlisting, basic auth, or any other protection you already have on the n8n side. A gateway removes the browser as an exposure point; it doesn't replace defense on the n8n side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will this work with n8n Cloud, or only self-hosted?&lt;/strong&gt;&lt;br&gt;
Both. The gateway only needs the webhook URL, and n8n Cloud and self-hosted instances both expose one in the same format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does CORS configuration alone hide the webhook URL?&lt;/strong&gt;&lt;br&gt;
No. CORS stops browsers from reading cross-origin responses, but the URL is still visible in your page's source and network requests, and CORS does nothing against requests sent outside a browser (curl, scripts, Postman).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I only have one workflow and one client?&lt;/strong&gt;&lt;br&gt;
A small serverless function you write and maintain yourself is a reasonable option at that scale. The build-your-own-proxy math changes once you add rate limiting, signed sessions, SSRF checks, and a second client to manage.&lt;/p&gt;

&lt;p&gt;If you're currently staring at a webhook URL in your own dev tools, that's the thing to fix first. &lt;a href="https://chatflowgate.com" rel="noopener noreferrer"&gt;chatflowgate.com&lt;/a&gt; has the docs for the embed and the free tier if you want to see the proxy behavior against a live n8n workflow.&lt;/p&gt;

</description>
      <category>api</category>
      <category>automation</category>
      <category>security</category>
    </item>
  </channel>
</rss>
