Secure Mode
Turn on Yedric secure mode to sign chat users with HMAC-SHA256 so sessions and history follow a verified identity across visits and devices.
Secure mode ties chat sessions to a verified user in your app. Without it, the widget uses an anonymous browser-local sessionId. With it, the widget sends a verifiable username plus a cryptographic signature (HMAC-SHA256), and the server scopes the user’s sessions and history to that identity, so prior messages can reappear across visits and devices.
When to use secure mode
Section titled “When to use secure mode”- You know who the end user is (logged-in app, Shopify admin, your dashboard’s customer).
- You want chat history to persist per-user across page loads, devices, or browsers.
- You want to protect sessions from being impersonated by changing the
usernameattribute.
If none of that matters, you don’t need secure mode.
Keys and secrets
Section titled “Keys and secrets”Every assistant has a widget API key with two parts:
| Value | Prefix | Who sees it |
|---|---|---|
| Public key | yk_… |
Embedded in HTML as the key attribute. Sent as X-Yedric-Key. |
| Secret | yks_… |
Server-side only. Never should be sent the browser. |
Get the pair from Agents → Widget → Code in the dashboard, or create/refresh via the API.
Domain restrictions
Section titled “Domain restrictions”A widget key can be limited to a list of allowed domains. That check now fails closed: a request carrying no Origin or Referer header at all is rejected, where previously the check was skipped.
Browsers always send one of those headers, so ordinary embeds are unaffected. This does matter for anything calling the widget API outside a browser, such as a server-side script or an integration test, since those send no origin by default. Either send an Origin header that is on the list, or use a key with no domain restrictions for that caller. A key with an empty domain list stays unrestricted, so this only affects keys you have actually limited.
See Yedric API Keys and Agents for the full key management flow.
Signing the username
Section titled “Signing the username”The signature is HMAC-SHA256(secret, username) encoded as lowercase hex.
signature = hex( HMAC-SHA256( key = "yks_your_secret", message = "jannette.parks@yedric.ai" ) )The username is whatever stable identifier makes sense for your app: an email, a UUID, a Shopify shop UUID, a numeric user id, as long as the exact same string goes into the HMAC on the server and the username attribute on the widget.
Node.js (server)
Section titled “Node.js (server)”import { createHmac } from 'node:crypto';
function signYedricUsername(secret, username) { return createHmac('sha256', secret).update(username).digest('hex');}
const signature = signYedricUsername( process.env.YEDRIC_WIDGET_SECRET, req.user.email,);res.render('page', { yedricKey: process.env.YEDRIC_WIDGET_KEY, username: req.user.email, signature });PHP (server)
Section titled “PHP (server)”$signature = hash_hmac('sha256', $username, $yedricSecret);A real Shopify embed computes the signature from the shop UUID and passes the full config to the view:
$uuid = Request::Shop()->uuid;$yedricSecret = Config::get('yedric.widget_secret'); // load from env/config, never commit$yedricKey = Config::get('yedric.widget_key');$yedricAgent = Config::get('yedric.agent_id');
$yedric = [ 'enabled' => !empty($yedricSecret) && !empty($yedricKey) && !empty($yedricAgent), 'key' => $yedricKey, 'agent_id' => $yedricAgent, 'username' => $uuid, 'signature' => hash_hmac('sha256', $uuid, $yedricSecret),];Then the view renders:
<?php if (!empty($yedric['enabled'])): ?> <script src="https://cdn.yedric.ai/widget.js?agent=<?= htmlspecialchars($yedric['agent_id'], ENT_QUOTES, 'UTF-8') ?>"></script> <yedric-widget key="<?= htmlspecialchars($yedric['key'], ENT_QUOTES, 'UTF-8') ?>" agent="<?= htmlspecialchars($yedric['agent_id'], ENT_QUOTES, 'UTF-8') ?>" username="<?= htmlspecialchars($yedric['username'], ENT_QUOTES, 'UTF-8') ?>" signature="<?= htmlspecialchars($yedric['signature'], ENT_QUOTES, 'UTF-8') ?>" ></yedric-widget><?php endif; ?>Browser SubtleCrypto (edge functions, workers)
Section titled “Browser SubtleCrypto (edge functions, workers)”Useful when your “server” is a Cloudflare Worker, Deno Deploy function, or anywhere without node:crypto.
async function signYedricUsername(secret, username) { const enc = new TextEncoder(); const key = await crypto.subtle.importKey( 'raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'], ); const sigBytes = await crypto.subtle.sign('HMAC', key, enc.encode(username)); return Array.from(new Uint8Array(sigBytes)) .map((b) => b.toString(16).padStart(2, '0')) .join('');}Never run this in the end-user’s browser, because it needs the raw secret.
Wiring it to the widget
Section titled “Wiring it to the widget”Render the signed bundle into HTML attributes:
<script src="https://cdn.yedric.ai/widget.js?agent=agt_123"></script><yedric-widget key="yk_abc…" agent="agt_123" username="jannette.parks@yedric.ai" signature="a1b2c3…"></yedric-widget>Carrying a conversation across widget instances
Section titled “Carrying a conversation across widget instances”Each <yedric-widget> is its own instance. A second widget (on another page, or in a separate iframe such as a fullscreen editor opened from a list view) loads the user’s default thread on mount, not whatever conversation was active in the first instance. To carry the active conversation across, hand off its session id.
-
Read the active session id on the source page:
const sessionId = window.Yedric.getSessionId(); // secure session id, or null before a session exists -
Mount the second instance with that id via the
resume-session-idattribute. Because it’s an attribute, you can server-render it into the iframe URL or set it before the element connects:<yedric-widgetkey="yk_abc…"agent="agt_123"username="user@example.com"signature="a1b2c3…"resume-session-id="widget-<derived>-<thread>"></yedric-widget>The second instance resumes that exact thread instead of the user’s default thread. The server still validates that the thread belongs to the verified user.
-
Re-sync the first instance when the second one closes, so messages added in it appear:
window.Yedric.refresh(); // re-fetch the active secure thread's messages
getSessionId() and refresh() are also available on the element (document.querySelector('yedric-widget')). All three are secure-mode features: getSessionId() falls back to the anonymous session id outside secure mode, and refresh() is a no-op when there is no active secure session.
Common pitfalls
Section titled “Common pitfalls”- Never ship
yks_…to the browser. Only the HMAC output (signature) goes into HTML. If your secret is ever exposed, rotate it viaPOST /api/agents/:id/key/refresh. - Username must match exactly. A trailing space, different capitalization, or a different field (
idvsemail) will produce a different HMAC and a401 Invalid signatureerror. Log the exact string you pass to the HMAC and to theusernameattribute. It must be byte-for-byte identical. - One signature per username, not per session. You don’t re-sign for every page or session; the signature is static until the username changes.
- Storing the secret in version control. Prefer env vars or a secrets manager. Even in a private repo, committing
yks_…makes rotation painful and broadens the blast radius of a leak. - Session prefix matters.
session-id-prefix="preview"scopes secure sessions under a different prefix than the defaultwidget. A user’s “widget” history and “preview” history are separate, so don’t mix them. - Key/secret pair mismatch. If you rotate the assistant’s key but forget to redeploy the new secret on your signing server, every request will return
401 Invalid signature. Keep them in sync.
