{"record":{"id":"cf7db87954cdc065","repo":"HeyPuter/puter","slug":"internal-error-cf7db8","errorCode":"internal_error","errorMessage":"TURN credential generation failed","messagePattern":"TURN credential generation failed","errorType":"http","errorClass":"HttpError","httpStatus":500,"severity":"error","filePath":"src/backend/controllers/peer/PeerController.ts","lineNumber":213,"sourceCode":"            `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 }),\n            },\n        );\n\n        if (!cfRes.ok) {\n            const body = await cfRes.text();\n            console.warn(\n                '[peer] Cloudflare TURN credential generation failed',\n                cfRes.status,\n                body,\n            );\n            throw new HttpError(500, 'TURN credential generation failed', {\n                legacyCode: 'internal_error',\n            });\n        }\n\n        const data = (await cfRes.json()) as { iceServers?: unknown };\n        res.json({ ttl, iceServers: data.iceServers });\n    };\n\n    /**\n     * POST /turn/ingest-usage — internal-only TURN egress metering. an external\n     * service that knows the usage information from cloudflare will send it to\n     * us here. Meters each record directly against the owning user via\n     * `services.metering.incrementUsage` multiplied by turn:egress-bytes cost.\n     */\n    #ingestUsage = async (req: Request, res: Response): Promise<void> => {\n        const cfg = this.config.peers;\n        if (!cfg || !cfg.internal_auth_secret) {\n            throw new HttpError(403, 'Forbidden', { legacyCode: 'forbidden' });","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/peer/PeerController.ts#L195-L231","documentation":"`POST /peer/generate-turn` called Cloudflare's TURN credential API and received a non-2xx response. The endpoint logs the status and body, then throws HTTP 500. This is an upstream failure — the local config is correct but Cloudflare rejected the request (bad token, invalid service ID, rate limit, or outage).","triggerScenarios":"Cloudflare returned 4xx (invalid API token, wrong service ID, expired credentials) or 5xx (Cloudflare-side outage). The `cfRes.ok` check fails, the body is logged, and the error propagates to the client.","commonSituations":"The Cloudflare API token expired or was revoked; the service ID doesn't match the token's account; Cloudflare rate-limited the credential generation; transient Cloudflare API outage.","solutions":["Check backend logs for the Cloudflare status code and body printed by the `console.warn`.","If 401/403: rotate the `cloudflare_turn_api_token` and verify it has permissions for the TURN service.","If 404: verify `cloudflare_turn_service_id` matches an active TURN service in your Cloudflare account.","If 429/5xx: retry after a brief delay — the failure is transient."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function getTurnCredentials(maxRetries = 2) {\n  for (let i = 0; i <= maxRetries; i++) {\n    try {\n      const res = await fetch('/peer/generate-turn', { method: 'POST' });\n      if (res.ok) return await res.json();\n      if (res.status === 500) throw new Error('upstream failure');\n      throw await res.json(); // non-retryable\n    } catch (e) {\n      if (i === maxRetries) return null; // give up, use STUN fallback\n      await new Promise(r => setTimeout(r, 1000 * (i + 1)));\n    }\n  }\n}","preventionTips":["Retry transient Cloudflare failures with exponential backoff (1–2 retries).","Check server logs for the Cloudflare status code to distinguish auth errors from outages.","Rotate the Cloudflare API token if failures are persistent (401/403)."],"tags":["webrtc","turn","cloudflare","upstream-failure","http-500","network"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}