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

  1. If TURN is needed, set `peers.turn.cloudflare_turn_service_id`, `peers.turn.cloudflare_turn_api_token`, and `peers.turn.ttl` in config.
  2. If TURN is not needed, handle 503 gracefully on the client and fall back to STUN-only or host candidates.
  3. Obtain the Cloudflare TURN service ID and API token from the Cloudflare dashboard (Realtime / Calls section).
  4. 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

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.