Authentication & Access Control
How to control what the bot can access and act on — and how to verify who a customer is before unlocking anything sensitive. Every tool and condition provider receives inputs; you decide which to trust and what to verify.
Access to private data and sensitive actions in Octocom is something you compose. You decide what the bot can reach, and what it must prove first. This page shows you how — from the simplest order lookup to strong, unforgeable customer verification.
Here's the whole idea in plain terms. Anything a customer simply tells the bot — an email address, an order number — is a claim, not proof. Someone else could type the same thing. So for anything sensitive, you don't act on the claim; you verify it, using something that can't be faked: a one-time code sent to the real account owner, a token handed over by your already-logged-in website, or the confirmed sender of an email. Once identity is established to the level you require, the bot can safely help. You choose how much assurance each situation needs — a package-tracking question and a refund don't warrant the same bar.
The principle: identity is a gate, and a gate is only as good as it is hard to get around. Treat customer-supplied claims as unproven, verify them by means an impostor can't reproduce, and unlock sensitive data and actions only on the strength of that verification.
This page assumes a working understanding of the Bot Operating Model, workflows, custom actions, and condition providers — this is where they come together.
Where the bot's inputs come from — and how much to trust each
Both custom actions and condition providers run your code and receive a context object. The single most important security habit is knowing, for every input, where it came from — because that determines whether you can trust it.
| Input source | What it is | How much to trust it |
|---|---|---|
Bot-collected arguments (context["args"]) | What the customer told the bot — the order ID or email they typed into chat | Unverified. Convenient and fine for public lookups. Never, on its own, proof of identity. |
Verified customer profile (context["customer"]) | Identity established by the channel itself — most importantly, the authenticated sender address on email | Strong, for what the channel actually proves. See per-channel notes below. |
Conversation metadata / widget-injected data (get_conversation_metadata, chat-widget:* keys) | Data your own website passes in — including opaque tokens the customer and the model never see | As strong as your website's own authentication. This is your best tool for friction-free, high-assurance identity. |
Browser session (context["browser_session"]) | The visitor's browsing context on web chat | Context, not identity. Useful for personalization, not for authorization. |
The mental model: args is a claim; everything else is evidence. Sensitive flows should turn a claim into evidence before proceeding.
A subtle but critical point on email. When a customer emails you,
context["customer"]["email"]is the address they actually sent from — validated upstream by SPF, DKIM, and DMARC (see per-channel notes). That is worlds apart from an address they type into the message body. Always authorize against the verified sender, never against a self-reported string.
Verification is a choice, not a requirement
Before reaching for any of the methods below, it's worth asking a sharper question: does this flow need verification at all? Often the better design isn't a stronger gate — it's a smaller blast radius.
Plenty of excellent, security-conscious businesses running at scale make a deliberate trade-off here. If most of their customers only ever know their email address and would struggle to produce a second factor, they may knowingly accept a small, well-understood risk rather than add friction that costs them far more in lost resolution than it ever saves.
What makes that reasonable is that the risk depends entirely on what you actually expose:
- Approving a return is harmless if you don't refund or reship until the item is physically back.
- Order cancellation isn't a concern if your flow simply doesn't offer it.
- Sharing tracking status is low-stakes if you never surface the customer's address or other sensitive details alongside it.
Design the bot's capabilities tightly enough and there may be nothing sensitive behind the gate at all — at which point light-touch identification (an email, an order number) is a perfectly sound, conscious choice.
Other businesses sit at the opposite end. For a telecom or an insurer, strong verification is simply expected — customers aren't surprised to be asked to prove who they are, so the friction is a non-issue and the assurance is well worth it. Those businesses design around the second factor rather than avoiding it.
The point: the methods below are tools, not obligations. You decide, flow by flow, where to sit between friction and risk — and sometimes the best answer is to remove the sensitivity rather than add a gate.
Turning a claim into verified identity
These are the building blocks for establishing identity to whatever level you need. Mix and match them per situation. In every case the model orchestrates the conversation, but the check is deterministic code that the model cannot talk its way past.
Multi-identifier matching
The lightest step up from a bare lookup: require two matching pieces of information and verify them together against your system — for example order ID and the email or name on the order. Your API filters on both, so possessing just one isn't enough. Sufficient for a great many everyday stores, and low-friction.
Two-phase one-time code (email or SMS)
The classic strong check, built from two actions:
- Request a code. A first action generates a one-time code, stores it on the conversation with
set_conversation_metadata, and sends it out-of-band — to the email on file (viasend_outbound_email) or by SMS (by calling your SMS provider from a Python action). Crucially, it's delivered to the real owner's inbox or phone, not to whoever is in the chat. - Verify the code. A second action compares the code the customer supplies against the stored one, and on success writes a durable
verified: truemarker to metadata.
Anyone who isn't the account owner never receives the code, so they can't pass step two. From then on, other tools and condition providers can simply require the verified marker.
Website-passed token (the friction-free gold standard)
If the customer is already logged in on your website, you can authenticate them in chat with zero extra steps and without the identity secret ever entering the conversation. Your site writes a short-lived token to the widget as custom data; it arrives as conversation metadata; and a Python action validates that token against your backend.
The model and the customer never see the token. It travels from your website to your backend for verification and is used only in code. This is the cleanest pattern available: strong assurance, no friction, and nothing sensitive exposed to the model at all.
The verified email sender
On the email channel, authentication can be automatic. If the sending address passes SPF/DKIM/DMARC and it matches an order or account, the customer is already authenticated — no code, no questions. Just remember to key off the verified sender, not a typed address.
Persisting verification
Because a check writes its result to conversation metadata, "this customer is verified" becomes durable state. Later actions and condition providers read it and decide accordingly — you verify once, then gate everything downstream on the result.
Where the gating actually happens: condition providers and variants
Authentication is only half the story; the other half is making sure a sensitive capability only exists once the conditions are right. This is exactly what condition providers and variants are for, and it's why they're the heart of access control.
Recall from the Bot Operating Model that a tool only exists for the model when the selected workflow variant grants it. A condition provider runs your code, evaluates real state, and picks the variant — so it's the natural place to both verify identity and decide how much power to hand over.
Consider tiering refund capability by customer standing:
Customer: "I'd like a refund."
→ Condition provider runs:
- looks up the customer in your systems (by verified email / token)
- checks: are they a VIP? any recent chargebacks? verified?
- returns conditions: { isVerified, isVip, hasChargebackHistory }
→ Variant 0: requires [hasChargebackHistory] → hand off to a human, no auto-refund tool
→ Variant 1: requires [isVip, isVerified] → variant carries the higher-limit refund tool
→ Variant 2: requires [isVerified] → variant carries the standard refund tool
→ Variant 3: default (unverified) → no refund tool at all; ask to verify firstThe customer's standing is computed from your systems inside the condition provider — never taken from anything the model was told. The unverified customer isn't "trusted less" by a polite instruction; the refund tool is simply not present in their variant. There is nothing to jailbreak, because there is nothing there.
This is the same lesson as the refund example in AI Security, viewed from the authentication side: verify in the condition provider, expose the capability only in the variant whose conditions are met, and re-validate inside the action itself.
What each channel gives you to work with
Different channels hand you different starting evidence. The verification building blocks above still apply everywhere; this is about what you get for free.
- Web chat. The richest toolkit: widget-injected custom data (including opaque tokens and, for logged-in sites, direct identity), browser session context, plus any of the code-based checks. If your site has its own login, prefer the website-token pattern.
- Email. Often authenticates itself. A sender that passes SPF, DKIM, and DMARC proves control of that mailbox; if it matches an order, the customer is verified automatically. If they write from a different address, fall back to the code-based or multi-identifier checks.
- Social media. Accounts are essentially never tied to an order, so the account itself proves nothing. Use the same code-based and multi-identifier checks as web chat (website login isn't available here).
- Mobile apps. The simplest strong option is the app's own authentication — the app renders the chat and controls identity, so pass that through (the same shape as a website token). Otherwise, the web-chat methods apply.
- Review platforms. Authentication generally doesn't apply — responses are public and no sensitive operations happen. Don't expose private data or actions here.
Building and reviewing this safely
These checks are just code and configuration — fully inspectable, testable, and reviewable. Every action and condition provider can be run from the dashboard test panel before it ever touches a live conversation, and every real bot turn exposes the exact tools, inputs, and logic it used.
Because it's all transparent, you can also build and review this configuration with a capable model through Octocom MCP — reading the precise prompt and tools of any turn, and having a model like Fable or Opus sanity-check an authentication flow before you ship it. Security here isn't a black box you have to take on faith; it's ordinary, auditable engineering.
In short. Treat what a customer tells the bot as an unproven claim, and know exactly where every input comes from. Turn claims into verified identity with means an impostor can't reproduce — a one-time code to the real owner, a token from your logged-in site, or a verified email sender. Then gate sensitive data and actions in condition providers and variants, so a capability only exists once the conditions are met. Do that, and there is no sensitive workflow you can't automate safely — you decide the inputs, the verification, and the gate, and the platform enforces every one of them for you.