HeyPuter/puter · warning · HttpError
response_timeout
response_timeout
Error message
TURN not configured
What it means
`POST /peer/generate-turn` requires Cloudflare TURN configuration (`cloudflare_turn_service_id`, `cloudflare_turn_api_token`, `ttl`) under `config.peers.turn`. If any of these are missing, the endpoint returns 503 — the TURN feature is intentionally unavailable, not broken. The `legacyCode` is `response_timeout` but the HTTP status is 503 Service Unavailable.
Source
Thrown at src/backend/controllers/peer/PeerController.ts:184
/** GET /peer/signaller-info — public, no auth required. */
#signallerInfo = (_req: Request, res: Response): void => {
res.json({
url: this.config.peers?.signaller_url ?? null,
fallbackIce: this.config.peers?.fallback_ice ?? [],
});
};
/** POST /peer/generate-turn — generate TURN credentials via Cloudflare. */
#generateTurn = async (req: Request, res: Response): Promise<void> => {
const cfg = this.config.peers;
if (
!cfg ||
!cfg.turn ||
!cfg.turn.cloudflare_turn_service_id ||
!cfg.turn.cloudflare_turn_api_token ||
!cfg.turn.ttl
) {
throw new HttpError(503, 'TURN not configured', {
legacyCode: 'response_timeout',
});
}
const serviceId = cfg.turn.cloudflare_turn_service_id;
const apiToken = cfg.turn.cloudflare_turn_api_token;
const ttl = cfg.turn.ttl;
const customIdentifier = actorToTurnIdentifier(req.actor);
const cfRes = await fetch(
`https://rtc.live.cloudflare.com/v1/turn/keys/${serviceId}/credentials/generate-ice-servers`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ ttl, customIdentifier }),View on GitHub (pinned to 908ec23eda)
Solutions
- If TURN is needed, set `peers.turn.cloudflare_turn_service_id`, `peers.turn.cloudflare_turn_api_token`, and `peers.turn.ttl` in config.
- If TURN is not needed, handle 503 gracefully on the client and fall back to STUN-only or host candidates.
- Obtain the Cloudflare TURN service ID and API token from the Cloudflare dashboard (Realtime / Calls section).
- Restart the backend after adding the config.
Example fix
// before (config.json — no peers.turn)
{ "peers": { "signaller_url": "..." } }
// after
{
"peers": {
"signaller_url": "...",
"turn": {
"cloudflare_turn_service_id": "<id>",
"cloudflare_turn_api_token": "<token>",
"ttl": 86400
}
}
} Defensive patterns
Strategy: fallback
Validate before calling
// Check signaller-info to see if TURN is configured before requesting it
const info = await fetch('/peer/signaller-info').then(r => r.json());
// If TURN isn't configured, fall back to STUN/host candidates only
if (!info.turn_configured) {
iceServers = stunOnlyServers;
} Try / catch
try {
const res = await fetch('/peer/generate-turn', { method: 'POST' });
if (res.ok) {
const { iceServers } = await res.json();
pc.setConfiguration({ iceServers });
}
} catch {
// TURN unavailable — use STUN-only fallback
pc.setConfiguration({ iceServers: stunOnlyServers });
} Prevention
- Treat TURN as optional — always have a STUN-only fallback for ICE negotiation.
- Self-hosters: provision all three TURN config fields together or none.
- Handle 503 on generate-turn gracefully in the WebRTC setup path.
When it happens
Trigger: Calling generate-turn on a deployment that hasn't configured Cloudflare TURN credentials. The config object `config.peers` is missing, or `config.peers.turn` is absent, or individual fields within it are blank.
Common situations: Self-hosting without WebRTC TURN support configured; a deployment that doesn't use Cloudflare's TURN service; the peer-calling feature is optional and TURN was never provisioned.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/f0c782a0b6d0c567.
Report an issue: GitHub.