koala73/worldmonitor · error · EmbedKeyUnavailableError
Convex embed key validation unavailable: http-${resp.status}
Error message
Convex embed key validation unavailable: http-${resp.status} What it means
After fetch succeeds, fetchFromConvex checks `resp.ok` and throws EmbedKeyUnavailableError with `http-<status>` when the Convex internal endpoint returns a non-2xx status. Like the other variants, this means validation is unavailable — it is not a statement that the submitted embed key is wrong.
Solutions
- Read the exact status from the error message; for 401/403, re-sync CONVEX_SERVER_SHARED_SECRET with the Convex deployment's configured shared secret and redeploy both sides.
- For 404, confirm the internal-validate-embed-key endpoint exists in the deployed Convex functions and deploy the latest Convex code.
- For 5xx, check Convex dashboard logs for the failing function invocation and fix the function error.
- Retry after resolving — transient 5xx/502/503 from the platform usually clears once the underlying cause is fixed.
Example fix
// before: mismatched secrets -> http-401 // worker: CONVEX_SERVER_SHARED_SECRET=old-secret // convex: secret = "new-secret" // after: same secret on both sides // worker: CONVEX_SERVER_SHARED_SECRET=new-secret // convex: secret = "new-secret"
Defensive patterns
Strategy: try-catch
Validate before calling
const probe = await fetch(`${process.env.CONVEX_SITE_URL}/api/internal-validate-embed-key`, {
method: 'POST',
headers: { 'x-convex-shared-secret': process.env.CONVEX_SERVER_SHARED_SECRET ?? '' },
body: JSON.stringify({ keyHash: 'probe' }),
});
if (probe.status === 401 || probe.status === 403) throw new Error('Shared secret out of sync with Convex deployment');
if (probe.status === 404) throw new Error('internal-validate-embed-key endpoint missing from Convex deployment'); Try / catch
try {
const verdict = await result(keyHash);
} catch (err) {
if (err instanceof EmbedKeyUnavailableError && /http-\d+$/.test(err.message)) {
const status = err.message.match(/http-(\d+)$/)?.[1];
console.error(`Convex validator returned HTTP ${status}; failing closed until config/deploy is fixed`);
return failClosed();
}
throw err;
} Prevention
- Rotate CONVEX_SERVER_SHARED_SECRET simultaneously in the Convex deployment and every consumer to avoid 401/403.
- Verify the internal endpoint is deployed (no 404) before shipping workers that depend on it.
- Monitor Convex function logs so 5xx responses are caught before embed-key validation traffic hits them.
- Alert on http-4xx/5xx variants of EmbedKeyUnavailableError — each status points at a distinct, fixable cause.
When it happens
Trigger: The Convex endpoint responds with any non-ok status: 401/403 when the `x-convex-shared-secret` header doesn't match the deployment's secret, 404 when internal-validate-embed-key isn't deployed, 5xx on a Convex function error.
Common situations: CONVEX_SERVER_SHARED_SECRET rotated on one side only (worker vs Convex deployment); internal endpoint not yet deployed or removed in a Convex push; Convex function throwing due to a bad request shape or internal failure; a proxy returning 502/503.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Sentry issues request failed: HTTP ${response.status} ${resp
- Convex embed key validation unavailable: missing-config
- ${operation} HTTP ${status}: ${safeCode}
- Exa ${endpoint.slice(1)} failed HTTP ${response.status}: ${d
- PRO_REQUIRED
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/b8c5a1c316d144aa.
Report an issue: GitHub.
Appendix: source
Thrown at server/_shared/embed-key.ts:159
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');
}
if (!resp.ok) {
throw new EmbedKeyUnavailableError(
`Convex embed key validation unavailable: http-${resp.status}`,
);
}
let value: unknown;
try {
value = await resp.json();
} catch {
throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: invalid-json');
}
if (value === null) return null;
if (!isEmbedKeyResult(value)) {
throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: invalid-payload');
}
return value;
}
View on GitHub (pinned to 7d06c8633d)