koala73/worldmonitor · warning · ValidationError
callbackUrl is required
Error message
callbackUrl is required
What it means
registerWebhook throws a 400 ValidationError on callbackUrl when (req.callbackUrl ?? '').trim() is empty — the field is missing, an empty string, or whitespace only. It fires after the API-key and PRO gates and before assertCallbackUrlRegistrationSafe, because there is nothing else to validate about an absent URL.
Source
Thrown at server/worldmonitor/shipping/v2/register-webhook.ts:52
// 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.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));
if (invalidCp) {
throw new ValidationError([
{ field: 'chokepointIds', description: `Unknown chokepoint ID: ${invalidCp}` },
]);
}
// alert_threshold is `optional int32` (#3242 followup #4) — undefined meansView on GitHub (pinned to eeab0a219f)
Solutions
- Set callbackUrl to a fully-formed https URL before calling RegisterWebhook
- Add client-side required-field validation so the request is never sent with an empty value
- Check exact field name and casing (callbackUrl) against the proto/request type
Example fix
// before
await registerWebhook({ callbackUrl: form.url ?? '', chokepointIds }); // '' -> 400
// after
const url = form.url?.trim();
if (!url) throw new RangeError('callbackUrl is required');
await registerWebhook({ callbackUrl: url, chokepointIds }); Defensive patterns
Strategy: validation
Validate before calling
const url = req.callbackUrl?.trim(); if (!url) throw new RangeError('callbackUrl is required'); Type guard
const isNonEmptyCallbackUrl = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;
Try / catch
catch (e) { if (e?.details?.some?.(d => d.field === 'callbackUrl')) { fix the request field and re-submit } else throw e; } Prevention
- Make callbackUrl a required, validated field in the client form/schema
- Reject empty or whitespace-only input before building the RPC request
- Use exact field name 'callbackUrl' — typos serialize as omission
When it happens
Trigger: POST RegisterWebhook with callbackUrl omitted (undefined), '' or ' '; a JSON body with a misnamed field (callbackURL, callback_url) so the expected property is undefined; form submitted with an empty URL input.
Common situations: UI form validation gap letting an empty field through; env var interpolation producing '' in CI; field renamed during a client refactor; optional chaining defaulting to empty string before the call.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- callbackUrl is not allowed
- callbackUrl is not a valid URL
- Unknown chokepoint ID: ${invalidCp}
- alertThreshold must be between 0 and 100
- callbackUrl must use https
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/5d21253d192845e4.
Report an issue: GitHub.