koala73/worldmonitor · error · ApiError

Invalid API key

Error message

Invalid API key

What it means

registerWebhook requires a valid user API key. When revalidation via validateUserApiKey returns null — the credential was supplied but does not match any active user key — the handler throws HTTP 401 'Invalid API key'. The subsequent generic check (apiKeyResult.required && !apiKeyResult.valid) raises the same 401 for any unvalidated required key.

Solutions

  1. Regenerate the API key and update your secrets/environment before retrying
  2. Confirm the correct auth header is populated with the user API key, trimmed of whitespace
  3. Verify you are hitting the environment (prod/staging) the key belongs to
  4. Distinguish 401 (bad key) from 503 (validation outage) — for 401, fix the credential; do not blind-retry

Example fix

// before
await client.registerWebhook({ callbackUrl }); // 401: stale key
// after
const key = process.env.WM_API_KEY?.trim();
if (!key || key === 'webhook-signing-secret') throw new Error('Set WM_API_KEY to your user API key');
await client.registerWebhook({ callbackUrl }, { apiKey: key });
Defensive patterns

Strategy: validation

Validate before calling

const key = process.env.WM_API_KEY;
if (!key?.trim()) throw new Error('WM_API_KEY missing — cannot register webhook');

Type guard

function isUsableCredential(k: unknown): k is string {
  return typeof k === 'string' && k.trim().length >= 20;
}

Try / catch

try {
  return await client.registerWebhook(req);
} catch (e) {
  if (e.status === 401) {
    // Do not retry; refresh credential then surface a config error
    throw new ConfigError('Invalid API key: regenerate and update secrets');
  }
  throw e;
}

Prevention

When it happens

Trigger: POSTing to register-webhook with a revoked, mistyped, or deleted API key; sending no recognizable user key to an endpoint that requires one.

Common situations: CI secrets pointing at a rotated key; using a sandbox key against production (or vice versa); missing header entirely; accidentally passing a webhook signing secret instead of the API key.

Understand the failure class

Related errors


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

Appendix: source

Thrown at server/worldmonitor/shipping/v2/register-webhook.ts:54

  // API key. Without forceKey, a Clerk-authenticated pro caller reaches this
  // handler with no API key, callerFingerprint() falls back to 'anon', and
  // every such caller collapses into a shared 'anon' owner bucket — letting
  // one Clerk-session holder enumerate/overwrite other tenants' webhooks.
  // Matches the legacy `api/v2/shipping/webhooks/[subscriberId]{,/[action]}.ts`
  // gate and the documented "X-WorldMonitor-Key required" contract in
  // docs/api-shipping-v2.mdx.
  const apiKeyResult = (await validateApiKey(ctx.request, { forceKey: true })) as {
    valid: boolean; required: boolean; error?: string; credential?: string;
  };
  if (apiKeyResult.error === USER_API_KEY_GATEWAY_VALIDATION_ERROR) {
    const credential = getHeaderApiKey(ctx.request) as string;
    let userKey;
    try {
      userKey = credential ? await validateUserApiKey(credential) : null;
    } catch {
      throw new ApiError(503, 'Service temporarily unavailable', '');
    }
    if (!userKey) throw new ApiError(401, 'Invalid API key', '');
    // Revalidate the credential rather than trusting a caller-supplied user ID.
    apiKeyResult.valid = true;
    apiKeyResult.credential = credential;
  }
  if (apiKeyResult.required && !apiKeyResult.valid) {
    throw new ApiError(401, apiKeyResult.error ?? 'API key required', '');
  }

  await requirePremiumRpcAccess(ctx.request, ApiError, 'PRO subscription required');

  const callbackUrl = (req.callbackUrl ?? '').trim();
  if (!callbackUrl) {
    throw new ValidationError([{ field: 'callbackUrl', description: 'callbackUrl is required' }]);
  }

  try {
    await assertCallbackUrlRegistrationSafe(callbackUrl);
  } catch (error) {

View on GitHub (pinned to 7d06c8633d)