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
- Set both CONVEX_SITE_URL and CONVEX_SERVER_SHARED_SECRET in the environment (deployment dashboard or local .env loaded through loadEnvFile()).
- Verify the exact variable names — no typos, no missing prefix — and that they are present in the environment of the process actually serving requests.
- Restart/redeploy the service after adding the variables so the new environment is picked up.
- 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
- Load local secrets through loadEnvFile() and never hand-roll env parsing.
- Add a startup assertion that both CONVEX_SITE_URL and CONVEX_SERVER_SHARED_SECRET are present in server deployments.
- Document both variables in deployment config so new environments provision them together.
- Distinguish 'validator unavailable' from 'key invalid' in your embed-key handling so missing config fails closed, not open.
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
- INVALID_ORIGIN
- Convex embed key validation unavailable: http-${resp.status}
- Service unavailable
- global fetch is unavailable — Node 18+ is required
- PRO_REQUIRED
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)