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

  1. 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.
  2. For 404, confirm the internal-validate-embed-key endpoint exists in the deployed Convex functions and deploy the latest Convex code.
  3. For 5xx, check Convex dashboard logs for the failing function invocation and fix the function error.
  4. 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

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


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)