Keys and allowed origins
Everything starts in the partner console. There you get your site's keys, upload the key your backend signs user tokens with, and list the origins your pages run on.
The partner console
The platform team gives you the console's address and invites each person on your team by email. Everyone signs in with their own account: a password and a security key or passkey. Codes by text message or email do not work for staff.
Each person has a role:
| Role | Can |
|---|---|
| Read-only | See every setting, change nothing |
| Developer | Also change the integration: keys, token keys, allowed origins, the messenger element |
| Admin | Also change the AI assistant's settings (the playbook, the daily cap, the model), and read the audit log |
Changing a key needs a recent sign-in: after 10 minutes the console asks for your password and security key again before it goes ahead. The audit log records every change, and every refused attempt.
Your sites
The platform team adds your sites, and each site has keys of its own. A partner with several sites picks one at the top of the Integration page.
A new site is in the sandbox, and its keys say so: pk_test_... and sk_test_.... Keys of a live site start pk_live_... and sk_live_.... Going live is not open yet (guide 6), so build and test everything in the sandbox.
The publishable key
The Keys card on the Integration page shows the site's publishable key, pk_test_ and 32 hex digits, with a Copy button. It names your site to the messenger: your pages put it in the loader, the mp-messenger element and the inbox's address, and your user tokens carry it as iss.
The publishable key is public. Anyone can read it in your pages, and that is fine: it opens nothing by itself.
The secret key
The secret key, sk_test_ and 64 hex digits, lets your backend send profiles (guide 2). It reaches only this site's profiles.
- On the Keys card, press Rotate secret key. For a site with no secret key yet, this makes the first one.
- The console shows the new key once. Copy it and store it as a secret on your servers, then press I have stored it. The platform keeps only a fingerprint of the key, so a lost key means rotating again.
- After a rotation, the previous key keeps working for 24 hours, so you can switch your servers over. The card shows until when. Revoke now ends it at once, for example when it may have leaked.
Keep the secret key on your servers: never in a page, an app, a repository or a log (Authentication).
The token signing key
Your backend signs a short user token for each signed-in user (guide 4), and the platform checks it with the public half of your key pair (Your key). The platform takes Ed25519 keys (algorithm EdDSA) and P-256 keys (ES256), and no other kind.
Make an Ed25519 key pair on the server that will sign, in a folder outside your project, so the private key never lands in a repository. For example with OpenSSL:
mkdir -p ~/.config/messenger && chmod 700 ~/.config/messenger
cd ~/.config/messenger
openssl genpkey -algorithm ed25519 -out token-key.pem
chmod 600 token-key.pem
openssl pkey -in token-key.pem -pubout -out token-key.pub.pem
Or, in the same folder, with Node alone:
node -e '
const { generateKeyPairSync } = require("node:crypto");
const { writeFileSync } = require("node:fs");
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
writeFileSync("token-key.pem", privateKey.export({ type: "pkcs8", format: "pem" }), { mode: 0o600 });
writeFileSync("token-key.pub.pem", publicKey.export({ type: "spki", format: "pem" }));
'
token-key.pem is the private key. It stays on your server, readable only by the process that signs. Add *.pem to your project's .gitignore too, so git never picks up a stray copy. token-key.pub.pem is the public key, which starts with -----BEGIN PUBLIC KEY-----.
Then, on the Integration page:
- In the User token keys card, press Upload public key.
- Paste the whole public key, from
BEGIN PUBLIC KEYtoEND PUBLIC KEY, and press Upload key. - The console shows the key's id, its
kid, such astk_and 24 hex digits. Your tokens carry it in their header, so the platform knows which key to check them with.
A site holds two token keys at most, so you can switch keys without a gap: upload the new key, sign with it, then remove the old one. Removing a key stops every token signed with it at once.
The Integration health card on the same page lists tokens that failed a check, by reason, such as an unknown kid, a bad signature or an expired token. A visitor whose token failed comes in as a guest, so this card is where you see that something is wrong.
Allowed origins
An origin is the scheme, host and port of your pages, such as https://www.spaguide.example. Each site lists the origins whose pages may run its messenger and frame its inbox.
An origin matches only the same scheme, host and port. https://spaguide.example and https://www.spaguide.example are two origins. So are http://localhost:4310 and http://127.0.0.1:4310, though both reach your own machine. List each origin your pages open at, and open them at an origin you listed.
On the Sites page, press Edit origins for the site and enter one origin per line:
https://hostorhttps://host:port, exactly as a browser writes it: a lowercase host, no path, no trailing slash, no wildcard, and no port when it is 443;- at most 20 per site.
The console takes only https:// origins, for a sandbox site as for a live one. Only a copy of the platform set up for local development also takes plain http:// origins, such as http://localhost:4310. So the mp-messenger element cannot run on a page you serve at http://localhost: try it on an https:// address, such as a staging host. The loader runs on any page until go-live, http://localhost included.
What the list does today:
- The messenger element works only on pages at these origins, and only once you switch it on for the site on the same page (guide 3).
- From go-live, only pages at the site's allowed origins can frame the messenger and the inbox; until then any page can (contracts/widget-host.md, Security; contracts/inbox-host.md, Security).
So list every origin that shows the messenger, and the origin of your owner dashboard, now. Then nothing changes for you on the day those checks start.
Where each credential belongs
| Credential | Looks like | Lives | Never goes |
|---|---|---|---|
| Publishable key | pk_test_... |
In your pages, and in your tokens as iss |
Nowhere it matters: it is public |
| Secret key | sk_test_... |
On your backend, as a secret: an environment variable, a file or your secret store | Into a browser, an app, a repository, a log or a URL |
| Token signing key | A private key file you make | On the server that signs tokens, outside your project's folder | Into a browser, an app, a repository or a log |
| The token key's public half | A PEM public key, with a kid the console gives it |
In the partner console | Nothing to hide |
| User token | A short JWT, new for every request | From your backend to your page to the messenger, in memory | Into a log, a URL or storage |
If a secret key leaks, rotate it and revoke the previous one at once. If a token signing key leaks, remove it in the console at once and upload a new one. A leaked key reaches only its own site.
Next: the profile lifecycle, so the platform has your profiles before the messenger shows them.