Skip to main content

Authentication

DialStack supports three authentication methods for different use cases.

MethodFormatScopeUse case
API keyssk_ prefixPlatform-wideServer-to-server integrations (provisioning, analytics)
Session tokensJWTSingle accountEmbedded UI components (call logs, voicemails)
User tokensJWTSingle userSoftphones and user-facing apps (WebRTC, call history)

API Keys

Your platform has two types of API key, each used in a different place:

KeyFormatWhere it's used
Secretsk_ prefixServer-side requests. Grants full access to your platform — keep it confidential.
Publishablepk_ prefixClient-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 in test mode — just swap in your sk_test_* key.

Keep It Secret

Never expose your secret key in client-side code, public repositories, or browser applications. Store it securely in environment variables on your server.

Test mode vs live mode

Every key — secret and publishable alike — comes in two variants:

VariantFormatActs on
Livesk_live_* / pk_live_*Live accounts
Testsk_test_* / pk_test_*Sandbox accounts

The variant you authenticate with decides which world your request acts in, 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 test key only sees sandbox accounts. Nothing touches the real world — no real carriers, no ringing phones, no charges — so you can build and run integrations safely. Live accounts are invisible to a test key, and sandbox accounts are invisible to a live key.

There is no mode switch on a request: the mode is determined entirely by the key prefix, so you change modes by changing which key you send. The same rule extends to webhook endpoints, which are scoped to the mode of the key that created them.

Best practice

Use test 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

Most endpoints require an account context. Include the DialStack-Account header:

curl https://api.dialstack.ai/v1/users \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"

Getting Your API Keys

API keys are provided during platform onboarding. Contact support@dialstack.ai if you need access.

Session Tokens

For embedded voice components in your frontend, create a session using the Account Session API. Sessions are account-scoped and expire after 1 hour.

import { DialStack } from '@dialstack/sdk/server';

const dialstack = new DialStack(process.env.DIALSTACK_API_KEY);

const session = await dialstack.accountSessions.create({
account: 'acct_01h2xcejqtf2nbrexx3vqjhp41',
components: {
call_logs: { enabled: true },
voicemails: { enabled: true },
},
});

// Pass session.client_secret to your frontend

The account is automatically derived from the JWT claims — no DialStack-Account header needed.

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.

When to use which

Use API keys when your backend is making requests on behalf of your platform. Use session tokens for embedded UI 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 │
│─────────────────────────────────────────────>│
  1. The user logs into your application using your own authentication
  2. Your backend calls POST /v1/user_sessions with the user's identity
  3. DialStack returns a user token (JWT)
  4. Your backend passes the token to the client
  5. The client uses the token to connect to the WebRTC signalling channel and access user-scoped REST endpoints

Platform Setup

Token exchange is coming soon

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 iss claim value in your JWTs
  • Audience — the aud claim value DialStack expects (typically dialstack)
  • 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: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Obtaining a User Token

const { client_secret, expires_at, user, account } = await dialstack.userSessions.create({
user: 'user_01h2xcejqtf2nbrexx3vqjhp42',
ttl_seconds: 3600, // optional; defaults to 86400 (24 hours), max 604800 (7 days)
});

Response:

{
"user": "user_01h2xcejqtf2nbrexx3vqjhp42",
"account": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"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 you request at mint time — 24 hours by default, 7 days at most. Mint a new session proactively (e.g., 5 minutes before expiry) to avoid interrupting active WebRTC sessions; the WebRTC SDK's onTokenExpiring callback is the hook for this.

Revoke a user's outstanding tokens when they log out or are offboarded:

curl -X POST https://api.dialstack.ai/v1/users/user_01h2xcejqtf2nbrexx3vqjhp42/revoke_sessions \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"

This invalidates every token minted for the user before that instant and tears down any active WebRTC session using one.

Scoped Access

User tokens grant access to a limited set of endpoints:

EndpointMethodDescriptionAvailability
/v1/webrtcWebSocketSignalling channel for WebRTC callsAvailable
/v1/webrtc/ice-serversGETTURN/STUN server credentialsAvailable
/v1/me/emergency-addressesGET, POST, DELETEE911 emergency addressesAvailable
/v1/meGETAuthenticated user's profileComing soon
/v1/me/callsGETUser's own call historyComing soon
/v1/voicemailsGETUser's voicemailsComing soon
/v1/voicemails/{id}GET, POST, DELETESingle voicemailComing soon
/v1/voicemails/{id}/transcriptGETVoicemail transcriptComing soon
/v1/me/presenceGET, PUTUser's presence statusComing 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 token type is rejected with 401 Unauthorized.

Error Responses

401 Unauthorized

{
"error": "Invalid API key",
"code": "authentication_failed"
}

Common causes:

  • Missing Authorization header
  • Invalid or expired API key / token
  • Wrong key format

403 Forbidden

{
"error": "You don't have permission to access this resource",
"code": "forbidden"
}

Common causes:

  • Trying to access another platform's resources
  • Using a user token on a platform-level endpoint
  • Using a session token on a user-scoped endpoint

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

Next Steps