{"record":{"id":"c5bb0955cfef8bed","repo":"koala73/worldmonitor","slug":"invalid-api-key-register-webhook","errorCode":null,"errorMessage":"Invalid API key","messagePattern":"Invalid API key","errorType":"http","errorClass":"ApiError","httpStatus":401,"severity":"error","filePath":"server/worldmonitor/shipping/v2/register-webhook.ts","lineNumber":54,"sourceCode":"  // API key. Without forceKey, a Clerk-authenticated pro caller reaches this\n  // handler with no API key, callerFingerprint() falls back to 'anon', and\n  // every such caller collapses into a shared 'anon' owner bucket — letting\n  // one Clerk-session holder enumerate/overwrite other tenants' webhooks.\n  // Matches the legacy `api/v2/shipping/webhooks/[subscriberId]{,/[action]}.ts`\n  // gate and the documented \"X-WorldMonitor-Key required\" contract in\n  // docs/api-shipping-v2.mdx.\n  const apiKeyResult = (await validateApiKey(ctx.request, { forceKey: true })) as {\n    valid: boolean; required: boolean; error?: string; credential?: string;\n  };\n  if (apiKeyResult.error === USER_API_KEY_GATEWAY_VALIDATION_ERROR) {\n    const credential = getHeaderApiKey(ctx.request) as string;\n    let userKey;\n    try {\n      userKey = credential ? await validateUserApiKey(credential) : null;\n    } catch {\n      throw new ApiError(503, 'Service temporarily unavailable', '');\n    }\n    if (!userKey) throw new ApiError(401, 'Invalid API key', '');\n    // Revalidate the credential rather than trusting a caller-supplied user ID.\n    apiKeyResult.valid = true;\n    apiKeyResult.credential = credential;\n  }\n  if (apiKeyResult.required && !apiKeyResult.valid) {\n    throw new ApiError(401, apiKeyResult.error ?? 'API key required', '');\n  }\n\n  await requirePremiumRpcAccess(ctx.request, ApiError, 'PRO subscription required');\n\n  const callbackUrl = (req.callbackUrl ?? '').trim();\n  if (!callbackUrl) {\n    throw new ValidationError([{ field: 'callbackUrl', description: 'callbackUrl is required' }]);\n  }\n\n  try {\n    await assertCallbackUrlRegistrationSafe(callbackUrl);\n  } catch (error) {","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/server/worldmonitor/shipping/v2/register-webhook.ts#L36-L72","documentation":"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.","triggerScenarios":"POSTing to register-webhook with a revoked, mistyped, or deleted API key; sending no recognizable user key to an endpoint that requires one.","commonSituations":"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.","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"],"exampleFix":"// before\nawait client.registerWebhook({ callbackUrl }); // 401: stale key\n// after\nconst key = process.env.WM_API_KEY?.trim();\nif (!key || key === 'webhook-signing-secret') throw new Error('Set WM_API_KEY to your user API key');\nawait client.registerWebhook({ callbackUrl }, { apiKey: key });","handlingStrategy":"validation","validationCode":"const key = process.env.WM_API_KEY;\nif (!key?.trim()) throw new Error('WM_API_KEY missing — cannot register webhook');","typeGuard":"function isUsableCredential(k: unknown): k is string {\n  return typeof k === 'string' && k.trim().length >= 20;\n}","tryCatchPattern":"try {\n  return await client.registerWebhook(req);\n} catch (e) {\n  if (e.status === 401) {\n    // Do not retry; refresh credential then surface a config error\n    throw new ConfigError('Invalid API key: regenerate and update secrets');\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["authentication","api-key","http-401","webhook"],"backgroundTag":"authentication-required","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}