koala73/worldmonitor · error · ApiError
API key required
Error message
API key required
What it means
registerWebhook requires an explicit API key via validateApiKey({ forceKey: true }), matching the legacy api/v2/shipping/webhooks/[subscriberId] gate and the documented X-WorldMonitor-Key contract. The key is required because webhook ownership is keyed on callerFingerprint() (a hash of the API key); a keyless Clerk pro caller would fall into the shared 'anon' bucket and could enumerate or overwrite other tenants' webhooks, so the handler rejects with 401 first.
Solutions
- Obtain a shipping v2 API key and send it as the X-WorldMonitor-Key header on RegisterWebhook calls
- Validate the key is current and active; re-issue if rotated or revoked
- Ensure server-to-server callers load the key from configuration at startup, not per-request from an optional env
Example fix
// before
await shippingClient.registerWebhook({ callbackUrl, chokepointIds }); // 401
// after
await shippingClient.registerWebhook(
{ callbackUrl, chokepointIds },
{ headers: { 'X-WorldMonitor-Key': process.env.WORLDMONITOR_API_KEY! } },
); Defensive patterns
Strategy: validation
Validate before calling
if (!process.env.WORLDMONITOR_API_KEY) throw new Error('X-WorldMonitor-Key missing — required by registerWebhook even for Clerk-authenticated callers'); Try / catch
catch (e) { if (e?.status === 401) { surface a configuration error for the API key; stop retrying } else throw e; } Prevention
- Attach X-WorldMonitor-Key on every registerWebhook/listWebhooks call via a shared client wrapper
- Store the key in a secret manager and validate presence at boot
- Key rotation: update clients atomically before revoking the old key
When it happens
Trigger: POST RegisterWebhook without the X-WorldMonitor-Key header while relying on a Clerk session; invalid/expired/revoked key; automated partner client that never had the key configured. The check runs before premium gating, callbackUrl validation, and the SSRF safety check.
Common situations: Partner onboarding that skipped key issuance; key rotated and the old one cached in the client; env var for the key missing in CI; frontend code accidentally calling the RPC with session cookies only.
Understand the failure class
Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.
Related errors
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/9a54453b7ac7c885.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/shipping/v2/register-webhook.ts:60
// 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) {
const message = error instanceof Error ? error.message : 'callbackUrl is not allowed';
throw new ValidationError([{ field: 'callbackUrl', description: message }]);
}
const chokepointIds = Array.isArray(req.chokepointIds) ? req.chokepointIds : [];
const invalidCp = chokepointIds.find(id => !VALID_CHOKEPOINT_IDS.has(id));View on GitHub (pinned to 7d06c8633d)