Skip to content

Ask the docs

Answers come from this documentation only, with sources. For account-specific issues, the docs route you to the right owner.

What can I help you find?

You are chatting with an AI assistant. It can make mistakes, so double-check important information.

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.

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.

In the Dialog dashboard, go to On your storefront → Embedded app.

  1. Address to embed: the iframe URL, with your public API key already in it. Copy it.
  2. Allowed origins: add the origin of the page that embeds the app, for example https://advisors.example.com. Only https:// origins, plus http://localhost for development. Up to 20. The app ignores messages from any other origin.
  3. 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.

<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.

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.

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.
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)
}
})

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.