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
- Regenerate the API key and update your secrets/environment before retrying
- Confirm the correct auth header is populated with the user API key, trimmed of whitespace
- Verify you are hitting the environment (prod/staging) the key belongs to
- 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
- Never retry 401s with the same credential
- Separate sandbox and production keys per environment
- Test the key with a cheap authenticated call before running registration flows
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
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)