{"record":{"id":"5d21253d192845e4","repo":"koala73/worldmonitor","slug":"callbackurl-is-required","errorCode":null,"errorMessage":"callbackUrl is required","messagePattern":"callbackUrl is required","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"warning","filePath":"server/worldmonitor/shipping/v2/register-webhook.ts","lineNumber":52,"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.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) {\n    const message = error instanceof Error ? error.message : 'callbackUrl is not allowed';\n    throw new ValidationError([{ field: 'callbackUrl', description: message }]);\n  }\n\n  const chokepointIds = Array.isArray(req.chokepointIds) ? req.chokepointIds : [];\n  const invalidCp = chokepointIds.find(id => !VALID_CHOKEPOINT_IDS.has(id));\n  if (invalidCp) {\n    throw new ValidationError([\n      { field: 'chokepointIds', description: `Unknown chokepoint ID: ${invalidCp}` },\n    ]);\n  }\n\n  // alert_threshold is `optional int32` (#3242 followup #4) — undefined means","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/server/worldmonitor/shipping/v2/register-webhook.ts#L34-L70","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait registerWebhook({ callbackUrl: form.url ?? '', chokepointIds }); // '' -> 400\n// after\nconst url = form.url?.trim();\nif (!url) throw new RangeError('callbackUrl is required');\nawait registerWebhook({ callbackUrl: url, chokepointIds });","handlingStrategy":"validation","validationCode":"const url = req.callbackUrl?.trim(); if (!url) throw new RangeError('callbackUrl is required');","typeGuard":"const isNonEmptyCallbackUrl = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;","tryCatchPattern":"catch (e) { if (e?.details?.some?.(d => d.field === 'callbackUrl')) { fix the request field and re-submit } else throw e; }","preventionTips":["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"],"tags":["validation","required-field","http-400","webhooks","callback-url"],"backgroundTag":"missing-required-argument","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}