{"record":{"id":"f0c782a0b6d0c567","repo":"HeyPuter/puter","slug":"response-timeout","errorCode":"response_timeout","errorMessage":"TURN not configured","messagePattern":"TURN not configured","errorType":"http","errorClass":"HttpError","httpStatus":503,"severity":"warning","filePath":"src/backend/controllers/peer/PeerController.ts","lineNumber":184,"sourceCode":"    /** GET /peer/signaller-info — public, no auth required. */\n    #signallerInfo = (_req: Request, res: Response): void => {\n        res.json({\n            url: this.config.peers?.signaller_url ?? null,\n            fallbackIce: this.config.peers?.fallback_ice ?? [],\n        });\n    };\n\n    /** POST /peer/generate-turn — generate TURN credentials via Cloudflare. */\n    #generateTurn = async (req: Request, res: Response): Promise<void> => {\n        const cfg = this.config.peers;\n        if (\n            !cfg ||\n            !cfg.turn ||\n            !cfg.turn.cloudflare_turn_service_id ||\n            !cfg.turn.cloudflare_turn_api_token ||\n            !cfg.turn.ttl\n        ) {\n            throw new HttpError(503, 'TURN not configured', {\n                legacyCode: 'response_timeout',\n            });\n        }\n        const serviceId = cfg.turn.cloudflare_turn_service_id;\n        const apiToken = cfg.turn.cloudflare_turn_api_token;\n        const ttl = cfg.turn.ttl;\n\n        const customIdentifier = actorToTurnIdentifier(req.actor);\n\n        const cfRes = await fetch(\n            `https://rtc.live.cloudflare.com/v1/turn/keys/${serviceId}/credentials/generate-ice-servers`,\n            {\n                method: 'POST',\n                headers: {\n                    Authorization: `Bearer ${apiToken}`,\n                    'Content-Type': 'application/json',\n                },\n                body: JSON.stringify({ ttl, customIdentifier }),","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/peer/PeerController.ts#L166-L202","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (config.json — no peers.turn)\n{ \"peers\": { \"signaller_url\": \"...\" } }\n\n// after\n{\n  \"peers\": {\n    \"signaller_url\": \"...\",\n    \"turn\": {\n      \"cloudflare_turn_service_id\": \"<id>\",\n      \"cloudflare_turn_api_token\": \"<token>\",\n      \"ttl\": 86400\n    }\n  }\n}","handlingStrategy":"fallback","validationCode":"// Check signaller-info to see if TURN is configured before requesting it\nconst info = await fetch('/peer/signaller-info').then(r => r.json());\n// If TURN isn't configured, fall back to STUN/host candidates only\nif (!info.turn_configured) {\n  iceServers = stunOnlyServers;\n}","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch('/peer/generate-turn', { method: 'POST' });\n  if (res.ok) {\n    const { iceServers } = await res.json();\n    pc.setConfiguration({ iceServers });\n  }\n} catch {\n  // TURN unavailable — use STUN-only fallback\n  pc.setConfiguration({ iceServers: stunOnlyServers });\n}","preventionTips":["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."],"tags":["webrtc","turn","config","cloudflare","self-hosting","http-503"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}