Getting a website AI chatbot right isn't just "wire up an LLM API" — the real work is designing conversation routing, lead capture, notifications, and abuse prevention together as one system. Skip any one of those pieces and you end up with either a chatbot that leaks price quotes it shouldn't, a lead that never reaches anyone's inbox, or a public endpoint quietly burning through your API budget. Below, we walk through the chatbot actually running on our own site, noise-and-signal.com: its real architecture, its four layers of abuse protection, and the honest, small-sample data from its first week live.
Architecture: One Route, Three Modes
Our chatbot runs through a single backend route using the Vercel AI SDK's streaming functions, converting the visitor's conversation history into a model-readable format and streaming the reply back to the front end in real time, with a 30-second execution cap per request. The model provider is OpenAI, and the model string in our code is gpt-5.6-luna.
Conversations run in one of three modes — quote, clarify, and general — set by the front end in each request, each with its own system prompt. All three modes share the same ground rules: never reveal a specific price in the conversation, always reply in Markdown, ask only one question per turn, and only ask for contact details once, and only after clear intent has been detected — never upfront.
The never-reveal-a-price rule lives in a shared base instruction that all three modes inherit, rather than being repeated across three separate prompts.
Lead Capture and Notifications
When a conversation includes contact details, a separate extraction step handles it on a best-effort basis: if the environment isn't configured with the AI service key that step needs, extraction simply no-ops silently rather than breaking the conversation. Once contact details are captured, a backend email service sends us a notification — one type for a new conversation starting, another for captured contact details — and both use an atomic database update (check it hasn't been sent yet, then mark it sent and return the result in one step) to prevent the same notification firing twice. The same graceful-degradation pattern applies here too: without the email service's key and destination address configured, notifications simply don't fire, with no impact on the visitor's chat experience.
All conversation data lives in two database tables — one for the conversation itself (country, region, city, referrer, UTM parameters, device type, and contact fields), one for individual messages. Both tables have row-level security enabled with deliberately no access policies defined, meaning only the server, using a service-role key, can read or write; the key used client-side has zero access — an extra layer of protection at the database level. For visitor location, we only read headers the hosting platform (Vercel) has already parsed for us; we never read or store a visitor's raw IP address at any point.
Four Layers of Abuse Prevention
A public chat endpoint with no protection is effectively an open invitation to burn through your LLM API budget for free. Our site currently stacks four layers:
- Same-origin request checks — validating the Origin/Referer/Sec-Fetch-Site headers to reject requests that didn't originate from our own site.
- Rate limiting, in two layers: an always-on, in-memory token bucket for the fastest response, plus a database-backed atomic counter to handle multiple server instances running concurrently. The current rule allows up to 20 requests per visitor per 10 minutes and 200 per day, identifying visitors by a salted hash of their IP rather than storing the raw address.
- Request-size and message-count caps — limits on total request body size, how many messages are retained per conversation, the total message count, and per-message character length, to stop an oversized request from overwhelming the system.
- Output-length caps — a ceiling on how many tokens a single reply can generate, which also covers any extra "thinking" tokens a reasoning model might produce.
Worth being honest about: this four-layer setup was added after the fact. In the earliest version, this public endpoint effectively spent our own OpenAI budget with no protection at all — same-origin checks, dual-layer rate limiting, size caps, and output caps were all added later. If you're planning your own website AI chatbot, the lesson from that timeline is to design these protections in before launch, not after someone abuses the endpoint.
Real Data from Week One
In the 7 days starting September 15, 2026 (UTC), our chatbot logged 3 conversations from 3 unique visitors (2 in Taipei, 1 in Kaohsiung, all on desktop, all in Taiwan). None of them left contact details, and every conversation had exactly 2 messages — one visitor message and one AI reply, with no follow-up.
More telling: the opening message in all 3 conversations matched, word for word, the text of our pre-built "one-tap starter question" buttons — meaning not a single visitor typed their own question that week. That's not the polished number a marketing page would show, but it's what honest early-stage data actually looks like: a small sample and shallow engagement. It's a useful reminder that week-one numbers aren't enough to draw conclusions from — you need a longer observation window before reading anything into them.
Next Steps
The real difficulty in a website AI chatbot isn't wiring an LLM API into your site — it's designing lead capture, notifications, and abuse prevention together as one system, and staying honest about what early data actually shows rather than only reporting the weeks that look good. If you're planning an AI chatbot for your own site or product, see our AI Agent integration service, or get in touch to talk through your scope — including which of these four protection layers your own setup is still missing.