# Protect an assistant with your own login via callbackUrl

`callbackUrl` is the field you use when a coreAI assistant should be available only to users logged into your own system. When the field is set, coreAI contacts your own URL with the user's token before the chat widget initializes, and only users your endpoint approves with HTTP 200 get to talk to the assistant. The login, the session, and the user database stay with you — coreAI does not own the logic for who is who.

## When to use callbackUrl

Use `callbackUrl` when the assistant answers from data that should not be public: customer portals, intranets, employee documents, or multi-tenant solutions where each user should only see their own. The typical recipe is that the user logs in with you, you embed the coreAI widget on a protected page, and the widget passes a token (your own session ID, a JWT, a signed one-time code — whatever you already use) that coreAI verifies against your endpoint before the chat starts.

If the assistant is meant to answer everyone visiting an open page, you don't need `callbackUrl`. Leave the field blank.

## Two ways to send the token

`callbackUrl` supports two formats, and coreAI picks the method based on how the URL is written:

- **`{TOKEN}` in the URL.** Write `https://my-system.com/validate?token={TOKEN}` and coreAI substitutes the placeholder with the user's token and calls the URL with GET (falling back to POST if GET returns 404). The placeholder can sit anywhere in the URL, including in the path: `https://my-system.com/validate/{TOKEN}` works too.
- **Bearer header.** Write a fixed URL without `{TOKEN}`, for example `https://my-system.com/api/validate`. coreAI then calls the URL with POST and adds `Authorization: Bearer <token>` (falling back to GET if POST returns 404).

Pick whichever is easier to bolt onto your existing auth stack. The bearer variant fits best when you already have a JSON API that validates tokens; the URL variant is easiest when the validator is a standalone endpoint.

## How your endpoint should respond

Your endpoint must respond HTTP 200 with a JSON body to let the user through. Anything else — 401, 403, 500, network errors — is treated as a denial, and the widget does not initialize. If the response includes a `status` field, the value must be `"success"`; anything else denies access even if the status code is 200.

The JSON response can also return one or more `external_id`s:

```json
{
  "status": "success",
  "external_id": ["customer-4711", "department-sales"]
}
```

When the field is present, coreAI locks the conversation to the data sources that match these `external_id`s in the assistant's knowledge base. This is the mechanism that makes multi-tenant portals practical: one assistant, one knowledge base, but each user only gets to see their own orders, their own organization's documents, their own content. You decide the scoping in your own endpoint — coreAI just reads the result.

## What gets disabled automatically

As soon as an assistant has a `callbackUrl` set, coreAI shuts off three surfaces that would otherwise bypass the check:

- **Public search demo** — the search endpoint for unpublished demo hits responds 403.
- **Direct widget link** — the shareable URL that opens the widget standalone in the browser returns 403.
- **MCP exposure** — the assistant is not registered as an MCP tool for AI agents.

There is no way around the callback once it is set. If you want to expose something publicly again, you have to remove `callbackUrl` or create a separate assistant for the open surface.

## Why the check runs once per widget load

The token is validated when the widget initializes — that is, once per page load — and not for every question the user asks or every message streamed back. That is a deliberate choice. Re-validating on every chat call would inject network latency from your endpoint between every question and every answer without delivering any real security gain: access to the assistant is already decided by the fact that the widget was loaded in an authenticated session. A compromised session on your side would let through regardless of how often coreAI asked.

In practice, that means your endpoint receives one HTTP request per time a user opens the page the assistant lives on. That is a realistic load to design for, even for endpoints doing heavy lookups against your database.