Security

What's actually in the code that keeps your workspace safe.

Two products, one shared stack. This page documents how sign-in is enforced, how your workspace is isolated from everyone else's, what data is sent to AI providers and what isn't, and what's stored where — at the level of detail a security reviewer needs, written so a non-developer can still follow it.

Auth
Clerk · SCCs
Database
Supabase EU
Transport
HTTPS only
Region
Frankfurt

Implemented controls

Eight things in the code today.

Each item below names a control, says what it actually does, and explains why it matters. These are not aspirational — every one of them is currently shipping. If you want to verify a specific claim, ask and we will walk you through the relevant code on a call.

AUTHcontrol 01

Sign-in protects every dashboard route

Every page under /dashboard and every management endpoint is guarded by Clerk session middleware. Before any server code runs, the middleware verifies the session cookie, decodes who the user is, and identifies which organization they're acting on behalf of. If verification fails the request is rejected with 401 Unauthorized before it ever touches the database. Sessions use HTTP-only, secure, SameSite cookies — they cannot be read by browser JavaScript and they cannot be sent on cross-site requests, which protects against the two most common session-theft techniques.

SCOPEcontrol 02

Your workspace is invisible to other workspaces

When you create an organization, that organization becomes your workspace and gets its own ID. Every record we create on your behalf — agents, prompts, conversations, contacts, knowledge items, offers, channel integrations — carries that workspace ID. Every database query the server makes filters on that ID before returning anything. If you don't use an organization, your user ID acts as the workspace ID; the rule is identical. There is no "super-admin" route in the codebase that can read across workspaces.

APIcontrol 03

Management routes require sign-in, no exceptions

Prompt generation, prompt history, agent CRUD, agent analytics, knowledge management, offers, conversations, channel connections, and CRM integrations all require a signed-in Clerk session. There is no API key escape hatch, no shared service account, and no "public mode" for management endpoints. Unauthenticated requests get 401 Unauthorized.

WIDGETcontrol 04

Public widget routes are deliberately walled off

Embeddable agent widgets cannot use sign-in because website visitors don't have accounts. Instead, the widget's eight public routes (chat, lead, handoff, csat, conversion, messages, transcript, widget bundle) accept only an agent's public token and resolve only that one specific agent. They cannot list other agents, read your dashboard, see another workspace, change settings, or access knowledge from a different agent — even if a malicious visitor inspects the network traffic.

SECRETScontrol 05

Credentials with database access live only on servers

Four credentials matter most: the OpenRouter API key (calls AI providers), the Supabase service role key (full database access), the Clerk secret key (issues sessions), and channel webhook secrets (verify inbound messages from WhatsApp, Telegram, Messenger, Instagram). All four exist only inside server code and environment variables — never in the browser bundle, never in the widget script, never in a public URL. Your browser receives only the Clerk publishable key (intended for client-side use), the Supabase anon key (which respects row-level security), and the public agent tokens (constrained to one agent each).

HISTORYcontrol 06

Conversation context is rebuilt from the database every turn

When a visitor sends a chat message, the server does not trust any prior conversation history the visitor's browser claims to have. It looks up the session, reads the last forty messages from the database in chronological order, and uses that as the source of truth. This makes prompt-injection-via-fake-history impossible — a visitor cannot fabricate a turn where "the agent agreed to give them 90% off" because the server doesn't read their version of the history.

RAGcontrol 07

Knowledge search is bounded to one agent at a time

Each chat message triggers a vector similarity search against the agent's knowledge base. The query is always parameterised by the agent's ID — the database is asked "find the most similar passages to this question, where agent_id equals THIS agent." Even if two of your agents share the same database (which they do, they're rows in the same table), neither can leak knowledge into the other's responses. Across workspaces the workspace-scoping rule applies on top: agents in different workspaces are completely isolated.

TLScontrol 08

HTTPS in transit, provider-managed encryption at rest

The dashboard and every API endpoint are served over HTTPS — the hosting platform enforces this; there is no plaintext fallback. Data at rest is encrypted by Supabase using their provider-managed disk encryption (EU region, Frankfurt). We deliberately do not add a custom application-layer encryption scheme on top — the security boundary is the workspace-scoping rule and the public-token boundary, not a custom crypto layer that would mostly add ways to lose data.

Data flows

Where your data actually travels.

Five operations cover almost everything the platform does. For each, this is the end-to-end path the data takes — from where it enters the system to where it ends up.

01

Prompt Studio (a prompt generation)

Your form is validated in the browser, then sent to /api/refine. The server checks your sign-in, checks your plan's allowed-models list, builds a meta-prompt that selects one of twenty prompt-engineering frameworks for your inputs, and forwards everything to OpenRouter using the server-side API key. The refined prompt streams back word by word over Server-Sent Events. If you choose to save the run, it's written to the prompt_history table tagged with your workspace ID — visible to your workspace, invisible to everyone else.

02

Advi Agents (a website chat)

The visitor's browser loads the widget script from /api/widget/[token] (a few kilobytes of JavaScript generated server-side from the agent's settings). When the visitor sends a message, the script POSTs it to /api/agent/[token]/chat. The server resolves the agent by token, finds or creates a session for that visitor, persists the message, rebuilds the conversation context from the database, generates an embedding for the question, runs a knowledge-base search bounded to that agent, injects matched passages into the system prompt, and streams the reply back. Every message — both directions — is written to the database for later review.

03

Knowledge base ingestion

When you upload a PDF or paste raw text into an agent's knowledge tab, the server extracts the text (pdf-parse for PDFs), splits long content into smaller chunks, generates a 1,536-number embedding for each chunk via the embeddings provider, and stores everything in the knowledge_items table tagged with that agent. The HNSW vector index built by pgvector makes searches fast enough to run on every visitor message without making the chat slow.

04

Analytics dashboard

When you open Analytics, the dashboard requests aggregate counts (sessions, messages, contacts, active conversations, channel distribution, recent conversations, CSAT) for either one agent or your whole workspace. Every query filters on your workspace ID before returning. The dashboard never receives another workspace's numbers, even by accident, because the filter happens server-side before the data leaves the database.

05

Channel webhooks

When a customer messages your agent on WhatsApp, Telegram, Messenger, or Instagram, the channel provider sends an HTTP request to /api/webhooks/[channel]/[agentToken]. The server first verifies the signature of the request against the channel's shared secret — a request without a valid signature is rejected immediately. Verified messages are persisted exactly like web-widget messages, the agent generates a reply, and the reply is pushed back to the channel via the channel's API. The transcript ends up in the same dashboard as web conversations, with the channel column distinguishing the source.

The stack

What you're trusting when you sign up.

Every vendor in the stack matters because their failure is, by definition, our failure. Here are the five that handle your data and what each is responsible for.

Authentication

Clerk — session middleware on every dashboard route, organizations + personal workspaces, optional multi-factor authentication, SOC 2 Type II certified.

Database

Supabase Postgres — Frankfurt region (EU), provider-managed encryption at rest, pgvector for knowledge embeddings, automated daily backups.

AI gateway

OpenRouter — server-side OpenAI SDK, plan-aware model allowlist enforced before forwarding, zero-retention mode requested where the provider offers it.

Hosting

Next.js on a managed platform (Vercel or Railway depending on environment), HTTPS enforced, standalone build output for predictable deployments.

Logging

Server logs persisted for the hosting platform's default retention window (typically 7–30 days). The application layer does not log message bodies or PII into request logs.

Production checklist

What to verify before exposing real customers to your agent.

The platform is safe out of the box, but a few configuration items move from "works" to "production-ready" when you do them deliberately. These are the seven we recommend running through before pointing real traffic at a deployment.

  • 01Use the production Clerk publishable key on the live site and restrict the allowed-redirect-URL list to your exact domains before launch.
  • 02Keep CLERK_SECRET_KEY, OPENROUTER_API_KEY, and SUPABASE_SERVICE_ROLE_KEY in your hosting platform's environment variables only — never check them into Git, never paste them into the widget config.
  • 03Disable any local TEST_API_KEY behavior in production builds and confirm test headers are not honored on the production deployment.
  • 04Conversation retention is enforced by a scheduled cleanup job (GET /api/cron/cleanup-conversations) that runs daily at 03:00 UTC and deletes sessions older than the plan window defined in app/lib/plans.ts — Plus retains 90 days, Pro 365 days, Team and Agency unlimited. Messages and events cascade via the foreign key. The job is secured by CRON_SECRET and the schedule is declared in vercel.json so it cannot drift out of source control.
  • 05Review per-customer data flows for GDPR processor requirements: sign DPAs where you operate as a controller, list our processors (Clerk, Supabase, OpenRouter, hosting, payment) when you sign DPAs with your own customers.
  • 06Add rate limiting on /api/agent/[token]/chat and /api/agent/[token]/lead before exposing the widget to untrusted traffic — these are the two endpoints that receive untrusted input from public visitors.
  • 07Rotate channel webhook secrets and the Supabase service role key on a defined cadence — quarterly is the typical recommendation; immediately if a team member with access leaves.

Compliance & review

Need a security review or a signed DPA for your deployment?

A standard Data Processing Agreement and a current sub-processor list are available on request — countersigned within one business day. For Transfer Impact Assessments, custom retention configuration, or a walkthrough of any specific control on this page, book a call and we'll respond the same business day.