route tokens
A route token lets a caller in without a login: a public chat box on a website, or a webhook from GitHub. The token is a 32-byte random string that maps to one JID. A POST to a URL with that token puts the body into an inbound message at the mapped JID. The JID prefix carries the routing intent. web: is for a browser chat, and hook: is for a webhook from GitHub, Linear or any other sender of JSON. Whoever has the URL has the whole credential.
One URL carries three independent things
A route-token URL combines three independent things.
- Routing: the token
- Which folder the URL feeds. A random secret that
routdlooks up in the store. To revoke it, delete the row. - Identity: an optional login
- Who posts. The same link serves everyone. A visitor with a login session posts as their real self, and every other visitor posts as
anon:<ip-hash>. The link stays the same, and only the session of the caller changes. - Context: optional instructions on the link
- How the agent handles what arrives through the link. Free text set when the token is created, such as “bug reports from the acme site; triage, do not chat back”.
routdstamps it on each message, and the agent sees it as a<link-context>block. Two links into the same folder can carry two different instructions.
Only the principal who was allowed to create the token sets the context.
One table serves two URLs
All tokens live in the route_tokens table. The store hashes the raw token to a lookup key and maps it to a JID. The row records the folder that created it and the folder it delivers to. The row stores the delivery folder and does not read it from the JID, because a JID with a suffix is ambiguous. web:acme/support reads exactly like a plain token for a folder named acme/support.
Two URLs read that table:
/chat/<token>/- Serves the chat widget on GET and appends a message on POST. It streams the reply over SSE to browsers and returns a plain response to other callers.
/hook/<token>- POST only. It appends the body, returns 204 and does not wait for the agent.
Either URL accepts any valid token. The JID kind, web: or hook:, sets sender attribution. It does not limit which URL works. Each issuance verb returns its own URL: issue_chat_link returns /chat/<token>/, and issue_webhook returns /hook/<token>. reference / tokens has the full column list, the payload shapes and the MCP tools for tokens.
The JID names the folder and the source
web:<folder> means an anonymous visitor chat at this folder. web:acme is the public chat URL of acme. Add a suffix to split one folder across pages. web:acme/support and web:acme/sales are different conversations with the same agent, and routing rules can tell them apart.
hook:<folder>/<source> means a webhook from source to folder. hook:acme/eng/github is GitHub events for the acme/eng folder. hook:acme/eng/linear/comments puts Linear comments in a stream separate from hook:acme/eng/linear/issues. The agent sees the <source> segment in the sender field of the inbound message.
The JID prefix splits routing. A hook: message and a web: message can match different rules, even though one table issued both tokens.
Agents and operators create tokens through one writer
Two sources create tokens, and one writer inserts the row:
- An agent over MCP
- A turn calls
issue_chat_linkorissue_webhookon the MCP socket of its group and gets the URL back once. The agent gives it to a person to paste. - An operator over REST or the CLI
arizuko token issue, the REST route behind OAuth, or adashdbutton creates the same token for a folder. reference / tokens has the exact signatures.
Both paths use the same writer, store.InsertRouteToken, with the same row shape and the same audit trail. The raw token is returned exactly once, and the store keeps only its sha256. If you lose the token, issue a new one.
A new folder gets no chat token automatically. A folder gets a token when someone calls issue_chat_link for it. So unused folders do not collect live URLs.
Both ways of creating a token take an optional context, the instructions per link described above. reference / tokens has the column shapes and the <link-context> contract.
Grants decide who can create a token for which folder
Grants decide this, as for everything else. The default role cannot create a public endpoint, so a plain folder cannot do it at all. It needs /root or a delegated mcp:issue_* row, and the scope glob of the row decides which folders the token can point at. reference / tokens has the table. An agent at acme with that grant over acme/** can create hook:acme/eng/github for the acme/eng child. The owner_folder of the row stays acme, the issuer, not the JID target. Revocation follows owner_folder, so acme keeps control of the tokens it created.
A revoke applies to the next request
A revoke is one DELETE on the row. The next request to the URL returns 404 at once, with no grace period and no cache. Every request looks up the token in the table. An agent in folder A cannot delete a token whose owner_folder = B: the ACL gate on revoke_token requires admin on owner_folder.
Whoever has the URL can post
Whoever has the URL can POST to it. This endpoint has no second factor, no signature on the body and no identity per caller. webd does not verify X-Hub-Signature or any other upstream HMAC: a skill on the agent side can check it, not the platform. To rotate, issue a new token, change the URL at the source and delete the old row.
webd limits the request rate per token in memory, and the JID prefix sets the limit. The limit is low for web:, which serves people, and higher for hook:, which serves bursts from machines. It limits the body to 1 MiB.
Five pages continue from here
- how-to: chat link: issue a chat URL, paste it on a site, and see what the visitor sees.
- how-to: webhooks: connect GitHub, Linear and custom alerts.
- reference: tokens: the full HTTP endpoints, payload shapes, SSE events and MCP tools.
- routing: how
web:<folder>andhook:<folder>/<source>reach an agent. - jid: the address shape on every platform.