Every call to POST /credentials/ice creates a new, single-purpose TURN credential. This page explains its lifecycle and how to choose a TTL.
What a credential is#
A credential is a TURN username and password, bundled with the STUN and TURN URLs in WebRTC's iceServers format:
- username: a random UUID, unique per request
- credential: a random 32-character hex password
- urls:
stun:stun.turnix.io:3478, plus four TURN URLs on the selected region's host<region>.turnix.io - expiresAt: the moment the credential stops being accepted, in ISO 8601 UTC
Turnix stores the credential in your project together with the region, the request's country, and any client and room labels. You can browse them under Credentials in the dashboard.
The TTL#
ttl is the credential's lifetime in seconds. Turnix sets expiresAt = now + ttl.
| Format | Whole seconds, digits only (3600, not 3600.0 or "1h"). Anything else returns 422 {"error":"TTL must be an integer"}. |
| Default | When you omit ttl, the platform default applies: 30 seconds. |
| Bounds | The API does not enforce a minimum or maximum. A ttl of 0 creates a credential that has already expired. |
30 seconds is only enough if the client uses the credentials immediately. If there is any delay between fetching credentials and creating the peer connection (a lobby, a permission prompt, a slow signaling round trip), set ttl explicitly.
When expiry matters#
The TURN server checks expiresAt when a client authenticates to create a relay allocation. That happens when ICE gathering starts, and again on every ICE restart, because a restart creates new allocations.
A relay that was set up before expiresAt is not cut off when the credential expires; the call continues. What fails after expiry is setting up new relays with the same credential, for example a renegotiation or an ICE restart after a network change.
Choosing a TTL#
| Scenario | Suggested ttl |
|---|---|
Credentials fetched right before new RTCPeerConnection() |
300–600 |
| Typical calls, credentials fetched when the user joins | 3600 |
| Long sessions where you cannot refresh before an ICE restart | Up to the longest expected session, for example 14400 |
Shorter is safer: a leaked credential can relay traffic, billed to you, until it expires. Longer is more forgiving of delays. Most apps are well served by 3600 plus a refresh before ICE restarts.
Refreshing credentials#
- New connection: request new credentials for every new
RTCPeerConnection. Requests are cheap and each one gets its own username. - ICE restart: if
expiresAthas passed or is close, fetch new credentials first and update the connection:
async function restartIceWithFreshCredentials(pc) {
const { iceServers } = await fetch("/api/turn-credentials").then((r) => r.json());
pc.setConfiguration({ ...pc.getConfiguration(), iceServers });
pc.restartIce();
}
- Do not cache credentials in
localStorageor reuse them across users.
Security checklist#
- Request credentials from your backend only. The API token must never reach a browser or app.
- Protect your credentials endpoint with your app's own authentication, so strangers cannot use it to get free TURN relay.
- Prefer short TTLs and refresh instead of long-lived credentials.
- If software you do not control needs TURN access (Nextcloud Talk, Jitsi), use shared-secret credentials instead of pasting a long-lived credential into its config.