Skip to main content
Lesson 2 of 16 · Voice Agent API: real-time conversations

Authentication and ephemeral tokens

Lesson 2 of 16 Updated July 30, 2026 Developer
On this page
  1. Two ways to authenticate
  2. Recommended architecture with ephemeral tokens
  3. Security best practices
  4. Key takeaways

Two ways to authenticate

The Voice Agent API offers two ways to authenticate your WebSocket connection. Which one you use depends on where your code runs: on the server or on the client.

Direct API key (server side)

The simplest method is to pass your API key in the authorization header when you open the WebSocket. This works for server applications, where the key stays protected:

const ws = new WebSocket("wss://api.x.ai/v1/realtime", {
  headers: {
    "Authorization": "Bearer xai-your-api-key"
  }
});

Never use this method in code that runs in the browser: the API key would be exposed in the source code or the developer tools.

Ephemeral tokens (client side)

For front-end apps (browser, mobile), xAI provides ephemeral tokens. Your backend generates a short-lived token, which you then pass to the client to open the WebSocket connection.

You create a token with a POST request:

curl -X POST "https://api.x.ai/v1/realtime/client_secrets" \
  -H "Authorization: Bearer $XAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expires_after": {"seconds": 300}}'

The expires_after.seconds field sets how long the token is valid, in seconds. The official documentation’s example uses 300 seconds (5 minutes): an ephemeral token is meant to cover a voice session, not to last. Aim for the shortest lifetime that works for your sessions.

Here’s the complete flow for a web app:

// 1. Server side (Node.js/Express): generate the token
app.post("/api/realtime-token", async (req, res) => {
  const response = await fetch(
    "https://api.x.ai/v1/realtime/client_secrets",
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.XAI_API_KEY}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ expires_after: { seconds: 600 } })
    }
  );
  // Return the xAI response body as is;
  // it contains the ephemeral token
  res.json(await response.json());
});

// 2. Client side (browser): use the token
// The browser's WebSocket constructor doesn't accept headers:
// the token goes in a SUBPROTOCOL, prefixed with "xai-client-secret."
async function startVoiceSession(ephemeralToken) {
  const ws = new WebSocket(
    "wss://api.x.ai/v1/realtime",
    [`xai-client-secret.${ephemeralToken}`]
  );

  ws.onopen = () => {
    console.log("Voice session started");
  };
}

Renewing tokens

The documentation doesn’t say whether an expiring token closes a connection that’s already open. Either way, your app needs a plan for when the connection drops: watch for the close event, ask your backend for a new token, then reconnect:

ws.onclose = async () => {
  // Connection lost or closed: request a new token
  const newToken = await fetchNewToken();
  reconnect(newToken);
};

Security best practices

A few essential rules keep your connections secure:

  • Never expose your main API key on the client
  • Keep the lifetime (expires_after.seconds) as short as possible: if your average session lasts five minutes, 300 to 600 seconds is enough
  • Validate token requests on the server (user authentication, rate limiting)
  • Log token creation so you can spot abnormal usage
  • Rotate your main API key if you suspect it has been compromised

Key takeaways

  • A direct API key is only suitable for server applications
  • Ephemeral tokens are a must for client-side apps (browser, mobile)
  • Set the token lifetime with expires_after.seconds and keep it short (300 seconds in the official example)
  • The recommended flow: your backend generates the token, and your front end passes it as the xai-client-secret. subprotocol to open the WebSocket
  • Always plan for automatic token renewal