Part 1
For business owners
What changes for your customers
- Nova knows the customer’s name and does not ask for an email you already have.
- Their conversation follows them. A chat started on a laptop is there on their phone once they sign in.
- If they chatted before signing in, that conversation moves to their account when they sign in.
- Your tools can act for that customer only, for example “Where is my order?” answered from their own order history.
- On WhatsApp, the customer’s number is already verified by WhatsApp, so the same tools work there too.
Why it is safe
Your website signs each customer id with a secret that only your server and Nova know. Nova trusts an id only when the signature matches, so nobody can type in someone else’s id and read their orders. Visitors who are not signed in never see the tools that need a customer, and Nova tells them to sign in instead. A customer access token, if you use one, is passed to your own server only. Nova never stores it and the AI never sees it.
What to do in the portal
-
Create your identity secret
In the Nova portal, open Channels › Installation and find Signed-in customers. Select Create secret.
The secret is shown once. Copy it and send it to your developer through a password manager or another private channel, never by plain email or chat.
-
Send this page to your developer
Part 2 below has everything they need. For most websites it takes under an hour. On Shopify it is one snippet in your theme.
-
Let your tools use the customer (optional)
Open MCP Servers, edit your server and go to Tools. Under each input, Filled by lets you choose Signed-in customer id, the customer’s email, their WhatsApp number, or a variable. The AI never sees or changes a value filled this way.
Values several tools share, such as a store id or an API key, go in the Variables card on the same page. Mark API keys as Secret so they are never shown again.
-
Decide who can chat (optional)
Turn on Only signed-in customers can chat if the chat is for account holders only, such as a members area. Everyone else is asked to sign in.
Where you see it
Signed-in customers appear in Contacts with their customer id under their name, and you can search by that id. In Conversations, the details panel shows a Signed in tag with the id, name and any extra details your website sent.
When to rotate the secret
Rotate it if it may have leaked or if someone who had it leaves your team. Rotating stops every existing signature at once, so signed-in customers are treated as visitors until your developer puts the new secret on your server. Plan it with them.
Part 2
For developers
How the signature works
For every signed-in customer, send Nova their id and a signature: the HMAC-SHA256 of the id, as text, keyed with the identity secret, written as lowercase hex. Make the signature on your server. The secret must never reach the browser.
Worked example: with the secret test-secret and the id 42, the signature is
0c448d9ba9697edc6e65d581ad07fba78e907baad3c1e452d3ead109363f97bf.
If your code gives a different value, check that the id is turned into text before signing.
Step 1. Sign the id on your server
// PHP
$userHash = hash_hmac('sha256', (string) $customer->id, getenv('NOVA_IDENTITY_SECRET'));
// Node
const userHash = crypto
.createHmac('sha256', process.env.NOVA_IDENTITY_SECRET)
.update(String(customer.id))
.digest('hex');
# Python
user_hash = hmac.new(os.environ['NOVA_IDENTITY_SECRET'].encode(),
str(customer.id).encode(), hashlib.sha256).hexdigest()
Step 2. Pass the customer to the widget
Use whichever fits your site. Only pages where the customer is signed in should carry these details.
Before the widget script. Best for pages your server renders for one customer.
<script>
window.kockatoosSettings = {
identity: {
userId: '42',
userHash: 'SIGNATURE_FROM_YOUR_SERVER',
name: 'Jane Doe',
email: 'jane@example.com',
attributes: { plan: 'gold' },
},
};
</script>
<script src="https://nova.kockatoos.co/widget.js" data-key="YOUR_WIDGET_KEY" async></script>
After the page loads. Best for single-page apps that learn who signed in later. window.kockatoosChat exists once the widget has loaded.
window.kockatoosChat.identify({
userId: customer.id,
userHash: signatureFromYourServer,
name: customer.name,
email: customer.email,
token: customerAccessToken, // optional
});
// When the customer signs out
window.kockatoosChat.logout();
On the script tag. The shortest form, for the id and signature only.
<script src="https://nova.kockatoos.co/widget.js" data-key="YOUR_WIDGET_KEY"
data-user-id="42" data-user-hash="SIGNATURE_FROM_YOUR_SERVER" async></script>
| Field | Required | Notes |
|---|---|---|
userId | Yes | Your customer id, up to 191 characters. Numbers are fine, they are signed as text. |
userHash | Yes | The signature from step 1. Without a valid one, Nova ignores userId. |
name | No | Shown to your team and used by the AI to greet the customer. |
email | No | Fills the customer’s contact so the chat does not ask for it. |
token | No | A customer access token for your own server, up to 4,096 characters. Sent with tool calls only, never stored. |
attributes | No | Up to 20 extra details such as a plan or loyalty tier. Names use lowercase letters, digits and underscores. Values up to 255 characters. |
Step 3. Sign the customer out
Call window.kockatoosChat.logout() when the customer signs out, so the next person on that browser starts a fresh chat.
As a safety net, the widget also drops a conversation when the page names a different customer, or nobody, on the next load.
Shopify
Paste this into theme.liquid in place of your current widget script, and replace the two placeholders. Shopify computes the signature on its servers, so the secret never reaches the page. Anyone who can edit your theme can read it, so keep theme access tight.
{% if customer %}
<script>
window.kockatoosSettings = {
identity: {
userId: '{{ customer.id }}',
userHash: '{{ customer.id | hmac_sha256: "YOUR_IDENTITY_SECRET" }}',
name: {{ customer.name | json }},
email: {{ customer.email | json }},
},
};
</script>
{% endif %}
<script src="https://nova.kockatoos.co/widget.js" data-key="YOUR_WIDGET_KEY" async></script>
Keep it safe
- Keep the identity secret in your server’s environment settings, never in front-end code or a public repository.
- Never print a customer access token into HTML that a CDN or page cache could serve to someone else. Pass it with
identify()after the page loads. - A signature never expires. If one leaks, it opens that customer’s chat history until the secret is rotated.
Using it in your tools
Your MCP server can receive the customer in two ways, both set in the portal’s MCP server dialog. Placeholders are written in double braces and filled in by Nova at the moment of the call.
| Placeholder | Value |
|---|---|
{{visitor.id}} | The signed-in customer id. Empty unless the signature is valid. |
{{visitor.phone}} | The customer’s number on WhatsApp, already verified by WhatsApp. |
{{visitor.email}}, {{visitor.name}} | As sent by your website. |
{{visitor.token}} | The customer access token from identify(). |
{{visitor.plan}} | Any attribute your website sent, by its name. |
{{vars.store_id}} | A workspace variable from the Variables card. |
Headers
Add headers such as Authorization: Bearer {{visitor.token}} or X-Store: {{vars.store_id}}.
They are sent with tool calls only, never when Nova reads the list of tools, and only to the server’s own address.
A header named Authorization replaces the server’s access token on tool calls.
Filled inputs
Set a tool input, for example customer_id, to be filled by {{visitor.id}}.
The AI no longer sees that input, so it cannot ask for or choose another customer. Nova adds the value after the AI decides to call the tool,
and the value is not saved in the conversation history.
When a value cannot be filled, for example a visitor who is not signed in, Nova leaves that tool out of the conversation and asks the visitor to sign in if they need it. Your server should still check every request it receives.
Troubleshooting
- Customers see “Please sign in to chat with us.”
- Only signed-in customers can chat is on, and the signature is missing or wrong. Compare your output with the worked example above, and check that your server uses the current secret.
- A tool never runs.
- One of its filled inputs had no value. The customer may not be signed in, an attribute may be missing, or a variable may have been renamed.
- Everyone stopped being recognised at once.
- The secret was probably rotated. Put the new one on your server.
- A new customer on a shared computer sees the previous chat.
- Call
logout()on sign-out, or make sure the page passes the new customer as soon as they sign in. - Does this work in the Flutter app?
- Not yet. Signed-in customers are supported on the website widget and, through the verified number, on WhatsApp.