Embedded app
The embedded app is the Dialog assistant packaged for sales advisors: a page your own advisor app loads in an iframe. The advisor talks with the assistant, scans or browses the catalog, keeps favourites lists and builds a pre-cart that Dialog turns into a Shopify draft order sent to the customer by email.
This page is for the team building the advisor app. It is not a storefront integration: for your website, see the SDK and the components.
How it fits together
Section titled “How it fits together”Your advisor app (page) Dialog iframe ─────────────────────────────────▶ assistant app ◀── dialog:ready ──▶ dialog:auth { token } (signed by your backend) ──▶ dialog:shopper { email } ◀── dialog:pre-cart-sent (draft order created; invoiceSent says whether it was emailed)Your backend holds a private key. For each logged-in advisor it signs a short-lived token naming that advisor; your page hands it to the iframe. Dialog verifies it with the public half you registered and scopes favourites and the pre-cart to that advisor.
Set up in the dashboard
Section titled “Set up in the dashboard”In the Dialog dashboard, go to On your storefront → Embedded app.
- Address to embed: the iframe URL, with your public API key already in it. Copy it.
- Allowed origins: add the origin of the page that embeds the app, for example
https://advisors.example.com. Onlyhttps://origins, plushttp://localhostfor development. Up to 20. The app ignores messages from any other origin. - Signing keys: click Generate a key. The pair is created in your browser; Dialog keeps the public half and shows the private key once. Store it with your backend secrets, with the key id (
kid). You can instead Import a public key of your own: an ES256 (P-256) key, as PEM or JWK.
Keys rotate without downtime: add a new key, deploy it, then revoke the old one. A revoked key is refused immediately.
Embed the app
Section titled “Embed the app”<iframe id="dialog-assistant" src="https://assistant-app.askdialog.com/?apiKey=YOUR_PUBLIC_API_KEY&locale=fr-FR" allow="camera"></iframe>| Parameter | Required | Description |
|---|---|---|
apiKey |
yes | Your public API key. Included in the address the dashboard gives you. |
locale |
no | A BCP 47 locale (fr-FR, en-GB). Sets the language and the market prices come from. Defaults to the browser’s. |
countryCode |
no | Two-letter country, when the market differs from the locale’s region. |
allow="camera" lets the advisor scan product QR codes. The app is laid out for a phone: give the iframe a portrait size, or scale it down to fit.
Building a native Android app? Load the app in a WebView instead: see Embedded app on Android.
Without a token the app still answers questions and browses the catalog. Favourites and the pre-cart appear once the advisor is authenticated.
Messages
Section titled “Messages”All messages go through postMessage. Post to the iframe’s exact origin, and accept only messages whose origin is that same origin and whose source is the iframe’s window.
From the app to your page
Section titled “From the app to your page”| Message | Payload | When |
|---|---|---|
dialog:ready |
none | The app is loaded and listening. Answer with dialog:auth, then dialog:shopper if known. |
dialog:token-expiring |
none | About 60 seconds before the token expires, or after Dialog refused it. Answer with a fresh dialog:auth. |
dialog:pre-cart-sent |
{ draftOrderName, invoiceUrl, invoiceSent } |
The pre-cart was sent: the draft order name (#D1) and the checkout link. invoiceSent is false when Shopify could not email it; the draft order exists either way, and the link is how the customer reaches it. |
From your page to the app
Section titled “From your page to the app”| Message | Payload | Role |
|---|---|---|
dialog:auth |
{ token } |
The advisor token. Send it on dialog:ready and on every dialog:token-expiring. |
dialog:shopper |
{ email?, customerId? } |
Who the advisor is serving. The email pre-fills the pre-cart form; customerId (a Shopify customer GID) attaches the draft order to that customer account. Send it again when the advisor switches customer. |
const frame = document.getElementById('dialog-assistant')const appOrigin = new URL(frame.src).origin
window.addEventListener('message', async event => { if (event.origin !== appOrigin || event.source !== frame.contentWindow) return
if (event.data.type === 'dialog:ready' || event.data.type === 'dialog:token-expiring') { const { token } = await fetch('/api/dialog-token').then(r => r.json()) frame.contentWindow.postMessage({ type: 'dialog:auth', token }, appOrigin) } if (event.data.type === 'dialog:pre-cart-sent') { console.log(event.data.draftOrderName, event.data.invoiceUrl, event.data.invoiceSent) }})The advisor token
Section titled “The advisor token”A JWT signed with ES256 by your backend, never in the browser. Your /api/dialog-token route signs one for the advisor who is logged in, and only for them.
| Claim | Value |
|---|---|
kid (header) |
The key id shown in the dashboard. |
iss |
Your organization slug, as in the dashboard URL. |
aud |
dialog-assistant-app |
sub |
The advisor’s id in your system. Opaque to Dialog; it keys their favourites and pre-cart. |
store |
Optional: the physical store’s id. |
iat, exp |
At most 15 minutes apart. Dialog allows 30 seconds of clock skew. |
With jose in Node:
import { importPKCS8, SignJWT } from 'jose'
const privateKey = await importPKCS8(process.env.DIALOG_PRIVATE_KEY, 'ES256')
const token = await new SignJWT({ store: 'paris-01' }) .setProtectedHeader({ alg: 'ES256', kid: process.env.DIALOG_KEY_ID }) .setIssuer('your-organization-slug') .setAudience('dialog-assistant-app') .setSubject(advisor.id) .setIssuedAt() .setExpirationTime('15m') .sign(privateKey)A token is refused when its key is unknown or revoked, its signature or audience is wrong, or it lives longer than 15 minutes. The app then asks your page for a new one with dialog:token-expiring.
Next steps
Section titled “Next steps”- Embedded app on Android: a native Android advisor app.
- SDK reference: the assistant on your own website.
- Catalog integration: the catalog the embedded app browses.
