Authentication
DialStack supports three authentication methods for different use cases.
| Method | Format | Scope | Use case |
|---|---|---|---|
| API keys | sk_ prefix | Platform-wide | Server-to-server integrations (provisioning, analytics) |
| Session tokens | JWT | Single account | Embedded UI components (call logs, voicemails) |
| User tokens | JWT | Single user | Softphones and user-facing apps (WebRTC, call history) |
API Keys
Your platform has two types of API key, each used in a different place:
| Key | Format | Where it's used |
|---|---|---|
| Secret | sk_ prefix | Server-side requests. Grants full access to your platform — keep it confidential. |
| Publishable | pk_ prefix | Client-side code. Safe to ship in a browser or mobile app; used to initialize the SDK. |
Your secret key is used for all server-side API requests:
curl https://api.dialstack.ai/v1/accounts \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
The same request works against your sandbox — just swap in your sandbox key (sk_test_*).
Never expose your secret key in client-side code, public repositories, or browser applications. Store it securely in environment variables on your server.
Sandbox vs live keys
Every key — secret and publishable alike — comes in two variants:
| Variant | Format | Acts on |
|---|---|---|
| Live | sk_live_* / pk_live_* | Live accounts |
| Sandbox | sk_test_* / pk_test_* | Sandbox accounts |
The variant you authenticate with decides which environment your request acts on, and the two are completely isolated:
- A live key only sees live accounts. Calls place on the real telephone network, ring real phones, and are billable.
- A sandbox key only sees sandbox accounts. Nothing reaches the public telephone network and nothing is billed, so you can build and run integrations safely. Live accounts are invisible to a sandbox key, and sandbox accounts are invisible to a live key.
There is no switch on the request itself: the environment is determined entirely by the key prefix, so you switch by changing which key you send. The same rule extends to webhook endpoints, which are scoped to the environment of the key that created them.
Use sandbox keys in development and CI, and reserve live keys for production. Never commit either key to version control, and never expose a secret key (sk_) in client-side code.
Account context
Your secret key covers your whole platform, so account-scoped requests also name the customer account they act on, in the DialStack-Account header. Which operations need it is covered in Making requests.
Getting Your API Keys
API keys are provided during platform onboarding. Contact support@dialstack.ai if you need access.
Session tokens
Embedded SDK components run in your customer's browser, so they can't hold your secret key. Instead, your backend mints a short-lived account session for one account, and the browser SDK uses its client_secret (a JWT) for every request.
┌─────────────┐ 1. Request session ┌─────────────────┐
│ Browser │ ───────────────────────▶ │ Your backend │
│ (SDK) │ │ │
│ │ ◀─────────────────────── │ │
│ │ 4. Return client_secret │ │
└─────────────┘ └────────┬────────┘
│ │
│ 5. API calls with 2. Create session
│ client_secret with secret key
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ DialStack API │
└──────────────────────────────────────────────────────────┘
- The SDK in the browser asks your backend for a session.
- Your backend checks that the signed-in user may act on the account, then creates a session with your secret key.
- DialStack returns a
client_secretscoped to that account. - Your backend returns it to the browser.
- The SDK sends it as the bearer token on every request, and fetches a new one before it expires.
Creating a session on your server
import { DialStack } from '@dialstack/sdk-server';
const dialstack = new DialStack(process.env.DIALSTACK_API_KEY);
app.post('/api/dialstack/session', async (req, res) => {
// Your own authorization check: may this signed-in user act on the account?
const accountId = await dialstackAccountFor(req.user);
if (!accountId) {
return res.status(403).json({ error: 'Access denied' });
}
const session = await dialstack.accountSessions.create({
account: accountId,
components: {
call_logs: { enabled: true },
voicemails: { enabled: true },
},
});
res.json(session); // { account, client_secret, expires_at, ... }
});
Each component you enable is a capability: the session can call only the operations those components need, and anything else is rejected with 403. At least one component must be enabled. See Account Sessions for every component, and the warning about agreement_acceptance.
Sessions can be created with a live or a sandbox secret key, and act on the matching environment. A session token cannot create further sessions.
Using the session in the browser
Initialize the SDK with your publishable key and a fetchClientSecret function that calls your backend:
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; // or { clientSecret, expiresAt } so the SDK refreshes before expiry
},
});
In React, pass that instance to the provider, and nest components inside it:
import { DialstackComponentsProvider } from '@dialstack/sdk-react';
import { CallLogs } from '@dialstack/sdk-react/call-logs';
export function App() {
return (
<DialstackComponentsProvider dialstack={dialstack}>
<CallLogs />
</DialstackComponentsProvider>
);
}
Without React, create Web Components from the same instance, for example dialstack.create('call-logs'). See Setup.
Expiry and refresh
Account sessions expire 1 hour after they're created. The SDK calls fetchClientSecret again on its own, so you don't refresh tokens by hand:
- If
fetchClientSecretreturns{ clientSecret, expiresAt }(expiresAtis the session'sexpires_at), the SDK fetches a new session about 5 minutes before that time. - If it returns only the
client_secretstring, the SDK assumes the session lasts 1 hour from when it was fetched.
Because the account is carried in the token, requests made with a session token don't need a DialStack-Account header.
User Tokens
For client-side applications where end users interact directly (softphones, call history, voicemail). User tokens are scoped to a single user within an account.
Use API keys when your backend is making requests on behalf of your platform. Use session tokens for embedded SDK components. Use user tokens when the end user's device connects to DialStack directly (WebRTC softphone, mobile app).
How User Tokens Work
┌──────────────┐ ┌──────────────────┐ ┌───────────────┐
│ Your App │ │ Your Backend │ │ DialStack │
│ (browser / │ │ │ │ │
│ mobile) │ │ │ │ │
└──────┬───────┘ └────────┬─────────┘ └───────┬───────┘
│ 1. User logs in │ │
│─────────────────────>│ │
│ │ 2. POST /v1/user_sessions
│ │──────────────────────>│
│ │ │
│ │ 3. { user_token } │
│ │<──────────────────────│
│ 4. Return token │ │
│<─────────────────────│ │
│ │ │
│ 5. Connect to /v1/webrtc with token │
│─────────────────────────────────────────────>│
- The user logs into your application using your own authentication
- Your backend calls
POST /v1/user_sessionswith the user's identity - DialStack returns a user token (JWT)
- Your backend passes the token to the client
- The client uses the token to connect to the WebRTC signalling channel and access user-scoped REST endpoints
Platform Setup
The POST /v1/auth/token token-exchange flow described below is documented ahead of release so you can review the design before it ships — it is not implemented yet. Today, mint user tokens with POST /v1/user_sessions using your secret API key — see Obtaining a User Token. If token exchange would (or wouldn't) fit your integration, we'd love to hear about it at api@dialstack.ai.
Configure token exchange for your platform during onboarding. This lets your backend exchange your own JWTs for DialStack user tokens — your users authenticate once with your app and get seamless access to DialStack calling with no additional login prompt.
Configuration requires:
- JWKS URL — where DialStack fetches your public keys to verify your JWTs
- Issuer — the
issclaim value in your JWTs - Audience — the
audclaim value DialStack expects (typicallydialstack) - User ID claim — which JWT claim maps to the DialStack user (typically
sub)
Contact your DialStack account team to configure these settings.
Mapping Users
Set the external_id field when creating users to match the user's identifier in your system:
const user = await dialstack.users.create(
{
name: 'Jane Doe',
email: 'jane@example.com',
external_id: 'your-system-user-id-123',
},
{ dialstackAccount: accountId }
);
Obtaining a User Token
- SDK
- cURL
const { client_secret, expires_at, user, account } = await dialstack.userSessions.create({
user: userId,
ttl_seconds: 3600, // optional; defaults to 86400 (24 hours), max 604800 (7 days)
});
curl -X POST https://api.dialstack.ai/v1/user_sessions \
-H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"user": "YOUR_USER_ID",
"ttl_seconds": 3600
}'
Response:
{
"user": "YOUR_USER_ID",
"account": "YOUR_ACCOUNT_ID",
"client_secret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-04-10T22:00:00Z"
}
Use client_secret as the Bearer token in the client.
Token Lifecycle
User tokens expire after the ttl_seconds you request when you mint them: 24 hours by default, 7 days at most. There is no refresh operation; to keep a user connected, mint a new token from POST /v1/user_sessions before the old one expires. The WebRTC SDK asks for one through its onTokenExpiring callback about 60 seconds before expiry, and swaps it in without dropping the connection or any call. See Token refresh.
Revoke a user's outstanding tokens when they log out or are offboarded:
curl -X POST https://api.dialstack.ai/v1/users/YOUR_USER_ID/revoke_sessions \
-H "Authorization: Bearer sk_test_YOUR_SECRET_KEY"
This invalidates every token minted for the user before that instant and tears down any active WebRTC session using one. With the Server SDK, call dialstack.users.revokeSessions(userId).
Scoped Access
User tokens grant access to a limited set of endpoints:
| Endpoint | Method | Description | Availability |
|---|---|---|---|
/v1/webrtc | WebSocket | Signalling channel for WebRTC calls | Available |
/v1/webrtc/ice-servers | GET | TURN/STUN server credentials | Available |
/v1/me/emergency-addresses | GET, POST, DELETE | E911 emergency addresses | Available |
/v1/me | GET | Authenticated user's profile | Coming soon |
/v1/me/calls | GET | User's own call history | Coming soon |
/v1/voicemails | GET | User's voicemails | Coming soon |
/v1/voicemails/{id} | GET, POST, DELETE | Single voicemail | Coming soon |
/v1/voicemails/{id}/transcript | GET | Voicemail transcript | Coming soon |
The Coming soon endpoints are documented ahead of release so you can review their design — they are not callable yet. If they would (or wouldn't) fit your use case as designed, tell us at api@dialstack.ai.
User tokens cannot access platform-level endpoints (accounts, phone numbers, dial plans, etc.). Those require API keys. Session tokens and user tokens are both JWTs but not interchangeable: using the wrong one is rejected with 401 Unauthorized.
Authentication errors
A rejected credential returns 401; valid credentials that don't reach the account or operation return 403. Error codes lists the causes of each.
Best Practices
- Store API keys in environment variables, never in client-side code
- Use secrets management tools (AWS Secrets Manager, HashiCorp Vault)
- Refresh user tokens proactively before expiry
- Revoke user tokens on logout
Reporting a Security Issue
If you believe you have found a vulnerability in the DialStack API, SDK, or platform, email security@dialstack.ai. Include enough detail to reproduce the issue, and please do not test against another platform's account or a real caller's data.
The same address is published at
/.well-known/security.txt.
If a secret API key has leaked, rotate it from your platform settings page in
the admin portal. If a user token has leaked, revoke that user's sessions with
POST /v1/users/{user_id}/revoke_sessions (see Token Lifecycle).
Do that first, then write to us.
Next Steps
- Quickstart Guide — Build your first integration
- Making requests — The
DialStack-Accountheader, retries and rate limits - WebRTC Guide — Build a softphone with user tokens
- API Reference — Full API documentation