koala73/worldmonitor · error · EmbedKeyUnavailableError

Convex embed key validation unavailable: missing-config

Error message

Convex embed key validation unavailable: missing-config

What it means

fetchFromConvex validates embed keys by POSTing to a Convex internal HTTP endpoint. It throws EmbedKeyUnavailableError with `missing-config` when either CONVEX_SITE_URL or CONVEX_SERVER_SHARED_SECRET is unset in the environment, because key validation cannot even be attempted. This is an availability error (the validator is unreachable), not a verdict that the key is invalid.

Solutions

  1. Set both CONVEX_SITE_URL and CONVEX_SERVER_SHARED_SECRET in the environment (deployment dashboard or local .env loaded through loadEnvFile()).
  2. Verify the exact variable names — no typos, no missing prefix — and that they are present in the environment of the process actually serving requests.
  3. Restart/redeploy the service after adding the variables so the new environment is picked up.
  4. Confirm the Convex deployment the URL points at has the internal-validate-embed-key endpoint and matching shared secret.

Example fix

// before: server crashes validation
// (no env vars set)

// after (.env loaded via loadEnvFile())
CONVEX_SITE_URL=https://your-deployment.convex.site
CONVEX_SERVER_SHARED_SECRET=<secret>
Defensive patterns

Strategy: validation

Validate before calling

if (!process.env.CONVEX_SITE_URL || !process.env.CONVEX_SERVER_SHARED_SECRET) {
  throw new Error('Set CONVEX_SITE_URL and CONVEX_SERVER_SHARED_SECRET before serving embed-key requests');
}

Type guard

function hasConvexConfig(env: NodeJS.ProcessEnv): env is NodeJS.ProcessEnv &
  { CONVEX_SITE_URL: string; CONVEX_SERVER_SHARED_SECRET: string } {
  return typeof env.CONVEX_SITE_URL === 'string' && env.CONVEX_SITE_URL.length > 0 &&
    typeof env.CONVEX_SERVER_SHARED_SECRET === 'string' && env.CONVEX_SERVER_SHARED_SECRET.length > 0;
}

Try / catch

try {
  const verdict = await result(keyHash);
} catch (err) {
  if (err instanceof EmbedKeyUnavailableError && err.message.endsWith('missing-config')) {
    console.error('Convex embed-key env vars are not set; failing closed (treat key as unvalidated)');
    return failClosed();
  }
  throw err;
}

Prevention

When it happens

Trigger: Any embed-key validation request where `process.env.CONVEX_SITE_URL` is empty/undefined OR `process.env.CONVEX_SERVER_SHARED_SECRET` is empty/undefined at the moment fetchFromConvex runs.

Common situations: Local development without the .env file loaded via loadEnvFile(); deploying the Railway worker or API without setting the two Convex variables; a typo'd env var name or a secret-scoping change that dropped the variables from one environment; CI running the server without secrets provisioned.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/1b457b8761423f23. Report an issue: GitHub.

Appendix: source

Thrown at server/_shared/embed-key.ts:139

      console.warn(`[embed-key] discarding non-conforming validation payload (type=${Array.isArray(result) ? 'array' : typeof result})`);
      return null;
    }
    return result;
  } catch (err) {
    // Transient Convex/network/config errors must stay retryable. Do not
    // collapse them into null — that would return a misleading 401.
    const unavailable = toUnavailableError(err);
    console.warn('[embed-key] validateEmbedKey unavailable:', unavailable.message);
    throw unavailable;
  }
}

/** Fetch key validation from the Convex internal endpoint. */
async function fetchFromConvex(keyHash: string): Promise<EmbedKeyResult | null> {
  const convexSiteUrl = process.env.CONVEX_SITE_URL;
  const convexSharedSecret = process.env.CONVEX_SERVER_SHARED_SECRET;
  if (!convexSiteUrl || !convexSharedSecret) {
    throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: missing-config');
  }

  let resp: Response;
  try {
    resp = await fetch(`${convexSiteUrl}/api/internal-validate-embed-key`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': 'worldmonitor-gateway/1.0',
        'x-convex-shared-secret': convexSharedSecret,
      },
      body: JSON.stringify({ keyHash }),
      signal: AbortSignal.timeout(3_000),
    });
  } catch {
    throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: fetch-error');
  }

View on GitHub (pinned to 7d06c8633d)