Quickstart
Render a branded interview in a React app in four steps.
Before you begin
Three values come from the Dialogue dashboard (or your Dialogue contact). If you just want to see it run first, skip to degraded anonymous mode — it needs none of them.
| Value | Where it lives | Where you get it |
|---|---|---|
studyId | A UUID for the study to run | Dashboard → the study you created |
Secret key (sk_…) | Server-side only — never ships to the browser | Dashboard → API keys |
publishableKey (pk_…) | Public; passed to the provider (reserved today) | Dashboard → API keys |
environment | 'production' (default) or 'staging' | Pick per Dialogue env |
- Install the packages
The styled drop-in needs
@dialogueai/react-uiand its peer@dialogueai/react.react18 is a peer dependency.bashpnpm add @dialogueai/react @dialogueai/react-ui - Expose a bootstrap-token endpoint
Your backend exchanges your secret key for a Dialogue-signed bootstrap token. The SDK calls a function you provide; it never sees your secret. (See Authentication.)
app/api/dialogue-token/route.tstsexport async function POST() { const res = await fetch('https://api.dialogueai.com/sdk/bootstrap-tokens', { method: 'POST', headers: { Authorization: `Bearer ${process.env.DIALOGUE_SECRET_KEY}` }, }); // { bootstrapToken, expiresAt } return Response.json(await res.json()); } - Mount the interview
Wrap your tree in
<DialogueProvider>and drop in<Interview>. Import the stylesheet once.InterviewWidget.tsxtsx'use client'; import { DialogueProvider } from '@dialogueai/react'; import { Interview } from '@dialogueai/react-ui'; import '@dialogueai/react-ui/styles.css'; export function InterviewWidget() { return ( <DialogueProvider publishableKey="pk_live_…" bootstrapTokenProvider={async () => { const res = await fetch('/api/dialogue-token', { method: 'POST' }); return res.json(); // { bootstrapToken, expiresAt } }} > <Interview studyId="019e16c4-ce3f-7388-b236-24455b07e857" appearance={{ layout: 'floating' }} /> </DialogueProvider> ); } - Handle completion
onCompletedfires when the interview itself finishes (payload{ interviewId, durationSeconds, completionToken }).onFinishfires later, when the participant clicks Finish to dismiss the widget. They are distinct: a participant can complete and then sit on the thank-you screen before dismissing.tsx<Interview studyId={studyId} onCompleted={({ interviewId, completionToken }) => { // completionToken may be null today (end-tokens not minted yet). // When present, send it to YOUR backend to verify — never trust the // client that an interview finished. void fetch('/api/dialogue-complete', { method: 'POST', body: JSON.stringify({ interviewId, completionToken }), }); }} onFinish={() => router.push('/thank-you')} // router: your app's router />
onCompleted as a UX signal; treat completionToken (once it ships) as something to verify on your backend, like a webhook signature.<Interview> renders exactly one screen per journey phase and drives every transition itself. You never manage phase state.Running without a backend
Omit bootstrapTokenProvider and the SDK runs in degraded anonymous mode: it mints an anonymous session directly. Useful for local development and demos before publishable keys are wired up. Identity is not carried across studies in this mode.
<DialogueProvider baseUrl="/dlg" debug>
<Interview studyId={studyId} />
</DialogueProvider>