Skip to main content

Secure Embed

Secure Embed lets you integrate a DronaHQ-published agent directly into your product while scoping every conversation to your own users — without requiring them to create a DronaHQ account.

The mechanism is straightforward: your backend signs a lightweight JWT using a secret you generate in the DronaHQ Agent Builder. You pass that token to the embedded agent (via the embed widget or a direct iframe). DronaHQ verifies the signature and automatically isolates conversation history per user based on the user_email you provide.

Diagram of Secure Embed Sharing

What you get

  • Per-user conversation history — no DronaHQ login required for your end users
  • Full control over user identification from your own auth system
  • Works with both the embed widget and direct iframe embedding
  • Automatic token refresh when JWTs expire mid-session

Quick Setup

Step 1: Generate your Identity Secret

Open your agent in the DronaHQ Agent Builder, click Share to open the publish side panel, and find the Secure Embed section.

Click Generate Secret and copy the value. Store it securely on your backend — treat it like a password.

danger

Never expose the Identity Secret in frontend code. It must only live on your server.


Step 2: Sign a JWT on your backend

Create an HS256-signed JWT containing your user's ID.

The token must include:

ClaimTypeRequiredDescription
user_emailstringYesYour app's unique user identifier
variablesobjectOptionalKey-value pairs that override agent variable values
expnumberRecommendedExpiration timestamp (Unix seconds)

Node.js

const jwt = require('jsonwebtoken');

const token = jwt.sign(
{
user_email: "<user_email>",
variables: {
name: "<user_name>",
}
},
process.env.DHQ_IDENTITY_SECRET,
{
algorithm: 'HS256',
expiresIn: '<expires_in>'
}
);

Using variables

The variables object lets you pass runtime values from your backend directly into the agent.

When a key inside variables matches a variable already defined in the agent's variable section, the JWT value takes precedence and overrides it for that session.

Assigning key inside variable-section
Assigning key inside variable-section

This is useful for personalising the agent with user-specific context — for example, passing in the user's name, role, or any other attribute your backend has at login time — without needing to configure those values statically inside the agent.

variables: {
name: "Jane Smith", // overrides the default value of `name` set in the agent
plan: "enterprise", // overrides the default value of `plan`
}

Any variable not included in the JWT payload continues to use the default value configured in the agent's variable section.


Step 3: Pass the token to the agent

Choose the integration method that fits your setup.

Direct iframe

Append IDENTITY_TOKEN and HIDE_LOGIN as URL query parameters:

<iframe
id="dronahq-agent"
src="https://app.dronahq.com/agent/YOUR_SLUG?IDENTITY_TOKEN=YOUR_JWT&HIDE_LOGIN=true"
style="width: 100%; height: 600px; border: none;"
></iframe>

To set the token dynamically:

const token = await fetchTokenFromYourBackend();

const iframe = document.getElementById('dronahq-agent');

iframe.src = `https://app.dronahq.com/agent/YOUR_SLUG?IDENTITY_TOKEN=${token}&HIDE_LOGIN=true`;

Automatic token refresh for iframes requires a postMessage listener — see the Automatic Token Refresh section below.


Automatic Token Refresh

When a JWT expires during an active conversation, the DronaHQ agent automatically requests a fresh token from the parent window using postMessage.

If a new token is provided, the failed request is retried seamlessly — the user never sees an error.

How it works

Diagram of Automatic Fresh Token
note

If the parent doesn't respond within 10 seconds (e.g. no listener configured), the agent falls back to showing an error modal asking the user to refresh the page.


Direct iframe

For direct iframe embeds, add a message event listener on your parent page:

const iframe = document.getElementById('dronahq-agent');

window.addEventListener('message', async (event) => {
if (event.data?.type === 'DHQ_IDENTITY_TOKEN_REFRESH_NEEDED') {
try {
const res = await fetch('/api/dronahq-token');
const { token } = await res.json();

iframe.contentWindow.postMessage({
type: 'DHQ_IDENTITY_TOKEN_REFRESHED',
identityToken: token,
}, '*');
} catch (error) {
console.error('Failed to refresh identity token:', error);
}
}
});

PostMessage Protocol

DirectionMessage TypePayload
iframe → parentDHQ_IDENTITY_TOKEN_REFRESH_NEEDED(none)
parent → iframeDHQ_IDENTITY_TOKEN_REFRESHED{ identityToken: string }

How It Works Under the Hood

  • DronaHQ verifies the JWT signature using your Identity Secret (HS256)
  • The user_email claim is extracted and used to scope conversations
  • Each unique user_email gets its own isolated conversation history
  • If no identity token is provided, users are anonymous (session-based)
  • If a user is logged into DronaHQ and has an identity token, the identity token takes priority

Secret Rotation

If you need to rotate your Identity Secret:

  1. Click Rotate Secret in the Secure Embed section of the publish panel
  2. Copy the new secret to your backend environment
  3. Redeploy your backend

⚠️ Warning: Rotating the secret invalidates all previously signed tokens immediately. Users with old tokens will see an "Authentication Failed" error until they receive a new token (via refresh or page reload).


Token Errors

ErrorCauseResolution
Session ExpiredJWT exp has passedHandled automatically if token refresh is configured; otherwise the user must refresh the page
Authentication FailedSignature doesn't match (secret was rotated?)Ensure your backend uses the current Identity Secret
Invalid Identity TokenMissing user_email claimAdd the required claim to your JWT payload
Identity Not ConfiguredNo secret has been generatedGenerate a secret in the publish panel