Ephemeral Tokens for WebSocket Connections
On this page
The problem: authenticating a client without exposing your key
When building a voice or real-time app with the Grok API, you need to establish a WebSocket connection directly from the browser or mobile client. Routing traffic through your server adds latency that breaks fluid voice conversation. But direct connections create an authentication problem: you must never include your API key in client-side code.
The reason is simple: any key embedded in frontend JavaScript, a decompilable mobile app, or a distributed binary is compromised the moment you publish it. Any user can open their browser dev tools and read it. To solve this, xAI offers ephemeral tokens: temporary tokens that clients can use without ever seeing the master key.
How ephemeral tokens work
The architecture follows one principle: the API key never leaves your server, and the client receives only a short-lived token. The flow is short. The client requests a token from your backend, which calls POST /v1/realtime/client_secrets with your API key. This call is the only place the key appears, and it happens entirely server-side. xAI returns an ephemeral token with its expiration date, which your backend passes to the client. The client then opens the WebSocket connection to wss://api.x.ai/v1/realtime presenting this token. At no point does the browser see the master key; it handles a token that expires on its own.
The creation call from your backend looks like this:
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": 600}}'
And the response:
{
"value": "Bearer eyJhbGciOiJSUzI1NiIs...",
"expires_at": 1712001200
}
Regarding duration, you have three benchmarks: there is no documented floor (a few seconds are possible), the default value is 600 seconds (10 minutes), and the maximum is 3600 seconds (1 hour).
Choosing the right duration
The token duration must match your use case, and this choice is a real security decision. For sporadic voice interactions—a Q&A, a voice command, dictation—a 300 to 600 second window is plenty: the session will end well before expiration. For sustained voice conversations like customer support, an interview, or an assisted meeting, aim for 600 to 1800 seconds to avoid cutting off mid-conversation. Prolonged work sessions—real-time transcription, continuous voice monitoring—may justify 1800 to 3600 seconds, but use the maximum duration cautiously: it expands the exploitation window if the token is stolen.
The golden rule sums up like this: always choose the shortest duration compatible with your use case. If a token is intercepted, a ten-minute expiration turns a potentially serious incident into a minor nuisance.
Backend implementation
Here is an example of a secure proxy in Node.js:
// Backend route - DO NOT expose client-side
app.post("/api/realtime-token", authenticateUser, 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 }
})
}
);
const token = await response.json();
res.json({ token: token.value, expires_at: token.expires_at });
});
Three details in this example carry the entire security of the setup. The API key lives in a server environment variable, never in the code. The authenticateUser middleware verifies the user’s identity before issuing a token—without it, anyone could obtain tokens and consume your quota at your expense. And only the ephemeral token, never the key, is returned to the client.
A few pitfalls come up regularly in code reviews. The API key slipped into the frontend “just for the prototype” invariably ends up in production. The token created systematically with the maximum duration, for convenience, when the use case requires a quarter of that. The generation endpoint left without authentication verification, open to everyone. And the lack of renewal handling on the client side, which produces expiration errors mid-session instead of a transparent reconnection: since the response gives you expires_at, your client has everything it needs to request a new token before the deadline rather than after.
Key takeaways
- Ephemeral tokens enable WebSocket authentication without exposing your API key
- Endpoint:
POST /v1/realtime/client_secrets(server-side call only) - Configurable duration from a few seconds up to 3600 seconds maximum (default: 600s)
- Your backend acts as an authentication proxy between the client and xAI
- Always choose the shortest duration compatible with your usage