HeyPuter/puter · error · HttpError

internal_error

internal_error

Error message

TURN credential generation failed

What it means

`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).

Source

Thrown at src/backend/controllers/peer/PeerController.ts:213

            `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 }),
            },
        );

        if (!cfRes.ok) {
            const body = await cfRes.text();
            console.warn(
                '[peer] Cloudflare TURN credential generation failed',
                cfRes.status,
                body,
            );
            throw new HttpError(500, 'TURN credential generation failed', {
                legacyCode: 'internal_error',
            });
        }

        const data = (await cfRes.json()) as { iceServers?: unknown };
        res.json({ ttl, iceServers: data.iceServers });
    };

    /**
     * POST /turn/ingest-usage — internal-only TURN egress metering. an external
     * service that knows the usage information from cloudflare will send it to
     * us here. Meters each record directly against the owning user via
     * `services.metering.incrementUsage` multiplied by turn:egress-bytes cost.
     */
    #ingestUsage = async (req: Request, res: Response): Promise<void> => {
        const cfg = this.config.peers;
        if (!cfg || !cfg.internal_auth_secret) {
            throw new HttpError(403, 'Forbidden', { legacyCode: 'forbidden' });

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Check backend logs for the Cloudflare status code and body printed by the `console.warn`.
  2. If 401/403: rotate the `cloudflare_turn_api_token` and verify it has permissions for the TURN service.
  3. If 404: verify `cloudflare_turn_service_id` matches an active TURN service in your Cloudflare account.
  4. If 429/5xx: retry after a brief delay — the failure is transient.
Defensive patterns

Strategy: retry

Try / catch

async function getTurnCredentials(maxRetries = 2) {
  for (let i = 0; i <= maxRetries; i++) {
    try {
      const res = await fetch('/peer/generate-turn', { method: 'POST' });
      if (res.ok) return await res.json();
      if (res.status === 500) throw new Error('upstream failure');
      throw await res.json(); // non-retryable
    } catch (e) {
      if (i === maxRetries) return null; // give up, use STUN fallback
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/cf7db87954cdc065. Report an issue: GitHub.