Skip to main content

Setup

Every embedded component needs an initialized SDK instance. You create it once, with your publishable key and a function that fetches a session client secret from your backend. In React you then hand the instance to DialstackComponentsProvider; with Web Components you hand it to each element.

The softphone is the one exception: it authenticates with a user token and needs no instance. See Softphone.

1. Create a session endpoint​

The browser never sees your secret key. Your backend creates a session for the account and returns its client_secret. See Sessions for the server code and Authentication for how sessions work.

2. Initialize the SDK​

Call loadDialstackAndInitialize once, at app startup:

TypeScript
import { loadDialstackAndInitialize } from '@dialstack/sdk-js';

const dialstack = await loadDialstackAndInitialize({
publishableKey: 'pk_live_YOUR_KEY',
fetchClientSecret: async () => {
const res = await fetch('/api/dialstack/session', { method: 'POST' });
const { client_secret } = await res.json();
return client_secret;
},
});
OptionTypeRequiredDescription
publishableKeystringYesYour publishable key (pk_live_… or pk_test_…)
fetchClientSecret() => Promise<ClientSecretResponse>YesFetches a session client secret from your backend
appearanceAppearanceOptionsNoTheme and design variables. See Theming
apiUrlstringNoAPI base URL. Defaults to https://api.dialstack.ai

The SDK calls fetchClientSecret right away and again before the session expires, so components keep working without a page reload. It can return the client secret as a string, or an object that also says when the session expires, so the SDK refreshes at the right time:

TypeScript
fetchClientSecret: async () => {
const res = await fetch('/api/dialstack/session', { method: 'POST' });
const { client_secret, expires_at, account_id } = await res.json();
return { clientSecret: client_secret, expiresAt: expires_at, accountId: account_id };
},

The object form also accepts the API's own field names ({ client_secret, expires_at, account_id }), so you can return the session response as is. When no expiry is given, the SDK assumes one hour and refreshes five minutes before that.

3. Connect your components​

Wrap your app, or the part that uses DialStack components, in DialstackComponentsProvider. Its only props are dialstack (the instance) and children; the instance fetches and refreshes the client secret itself.

TypeScript
import { useEffect, useState } from 'react';
import { loadDialstackAndInitialize, type DialStackInstance } from '@dialstack/sdk-js';
import { DialstackComponentsProvider } from '@dialstack/sdk-react';
import { CallLogs } from '@dialstack/sdk-react/call-logs';

// Created once, outside any component
const dialstackPromise = loadDialstackAndInitialize({
publishableKey: 'pk_live_YOUR_KEY',
fetchClientSecret: async () => {
const res = await fetch('/api/dialstack/session', { method: 'POST' });
const { client_secret } = await res.json();
return client_secret;
},
});

export function App() {
const [dialstack, setDialstack] = useState<DialStackInstance | null>(null);

useEffect(() => {
dialstackPromise.then(setDialstack);
}, []);

if (!dialstack) return <div>Loading…</div>;

return (
<DialstackComponentsProvider dialstack={dialstack}>
<CallLogs />
</DialstackComponentsProvider>
);
}

Each component has its own import path (@dialstack/sdk-react/call-logs, /voicemails, …). The provider and hooks come from the @dialstack/sdk-react root.

Hooks​

Inside the provider, useDialstack() returns the instance, for example to call the API directly. useDialstackComponents() returns { dialstack }.

TypeScript
import { useDialstack } from '@dialstack/sdk-react';

function UnreadCount() {
const dialstack = useDialstack();
// e.g. dialstack.voicemails.list(...)
}

Missing provider​

A component or hook rendered outside the provider throws:

Text
Could not find DialStack context; You need to wrap your app in a <DialstackComponentsProvider> provider.

Move the component inside <DialstackComponentsProvider>.

More than one account​

A session belongs to one account, so create one instance per account, each with its own fetchClientSecret, and give each part of the page its own provider:

TypeScript
<DialstackComponentsProvider dialstack={accountA}>
<CallLogs />
</DialstackComponentsProvider>
<DialstackComponentsProvider dialstack={accountB}>
<CallLogs />
</DialstackComponentsProvider>

The instance​

The object loadDialstackAndInitialize returns is available right away; calls made before the first session arrives wait for it.

create(tagName)​

Creates a component element wired to the instance. With /pure, await registerComponents() first.

TypeScript
const voicemails = dialstack.create('voicemails');
voicemails.setUserId(userId);
document.getElementById('container').appendChild(voicemails);

Tag names: 'call-logs', 'call-history', 'voicemails', 'phone-numbers', 'phone-number-ordering', 'ai-agent'.

update(options)​

Changes the appearance of every component the instance drives, React or Web Component:

TypeScript
dialstack.update({
appearance: {
theme: 'dark',
variables: { colorPrimary: '#8B5CF6' },
},
});

logout()​

Clears the session and its refresh timer and clears the components. Call it when your user signs out.

TypeScript
await dialstack.logout();

The instance also carries the REST client (dialstack.calls, dialstack.voicemails, dialstack.phoneNumbers, …) and the live event subscription (dialstack.on, dialstack.off). The SDK reference lists them all.

Handling API errors​

Calls made through the instance reject with an ApiError, which carries the HTTP status and, when the API returns one, a machine-readable code. Use isApiError to narrow an unknown error:

TypeScript
import { isApiError } from '@dialstack/sdk-js';

try {
await dialstack.phoneNumbers.list({ limit: 20 });
} catch (err) {
if (isApiError(err)) {
console.error(err.status, err.code, err.message);
} else {
throw err;
}
}

Prefer isApiError(err) over err instanceof ApiError. The error is created inside @dialstack/sdk-js but caught in your code, and instanceof compares against one specific class, so it can return false for a genuine API error, for example when the error crosses an iframe or worker boundary or when two copies of the package are installed. isApiError checks the error's own fields instead.

Framework examples​

With Web Components in a framework, initialize once and pass the instance to the element after it mounts. Importing from the package root registers the elements.

Vue
<template>
<dialstack-call-logs ref="callLogs"></dialstack-call-logs>
</template>

<script setup>
import { onMounted, ref } from 'vue';
import { loadDialstackAndInitialize } from '@dialstack/sdk-js';

const callLogs = ref(null);

onMounted(async () => {
const dialstack = await loadDialstackAndInitialize({
publishableKey: 'pk_live_YOUR_KEY',
fetchClientSecret: async () => {
const res = await fetch('/api/dialstack/session', { method: 'POST' });
const { client_secret } = await res.json();
return client_secret;
},
});

callLogs.value.setInstance(dialstack);
});
</script>

Tell Vue that dialstack-* tags are custom elements (compilerOptions.isCustomElement) so it doesn't try to resolve them as Vue components.

Next steps​