koala73/worldmonitor · warning · Error
callbackUrl is not a valid URL
Error message
callbackUrl is not a valid URL
What it means
First static check inside assertCallbackUrlRegistrationSafe: isBlockedCallbackUrl runs new URL(rawUrl), and a parse throw returns 'callbackUrl is not a valid URL', which line 121 rethrows and registerWebhook surfaces as a 400 ValidationError on callbackUrl. It catches structurally unparseable URLs before any protocol, hostname, or DNS check runs.
Source
Thrown at server/worldmonitor/shipping/v2/webhook-shared.ts:121
return (data.Answer ?? [])
.filter(answer => answer.type === expectedType && typeof answer.data === 'string')
.map(answer => answer.data!);
};
const records = await Promise.all([resolveRecordType('A'), resolveRecordType('AAAA')]);
return records.flat();
}
/**
* Validate the current DNS answer before storing a webhook. Delivery makes the
* same check immediately before send and pins the resulting socket, which
* keeps this fail-fast check from becoming the only SSRF control.
*/
export async function assertCallbackUrlRegistrationSafe(
callbackUrl: string,
resolveHostname: ResolveHostname = defaultResolveHostname,
): Promise<void> {
const staticError = isBlockedCallbackUrl(callbackUrl);
if (staticError) throw new Error(staticError);
const hostname = new URL(callbackUrl).hostname.toLowerCase();
if (isIpLiteral(hostname)) return;
let resolvedAddresses: string[];
try {
resolvedAddresses = await resolveHostname(hostname);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`callbackUrl DNS resolution failed: ${message}`);
}
if (!resolvedAddresses.length) throw new Error('callbackUrl DNS resolution returned no addresses');
const blocked = resolvedAddresses.find(isBlockedResolvedAddress);
if (blocked) throw new Error('callbackUrl resolves to a private/reserved address');
}
export async function generateSecret(): Promise<string> {
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);View on GitHub (pinned to eeab0a219f)
Solutions
- Always build the callback URL with an explicit scheme: `https://${host}${path}`
- Validate with new URL(url) client-side before calling RegisterWebhook
- Sanitize config-sourced URLs (trim, reject empty/undefined) at startup or form-submit time
Example fix
// before
registerWebhook(ctx, { callbackUrl: `${HOST}/cb`, chokepointIds }); // HOST='api.example.com' -> no scheme
// after
registerWebhook(ctx, { callbackUrl: `https://${HOST}/cb`, chokepointIds }); Defensive patterns
Strategy: validation
Validate before calling
try { new URL(callbackUrl); } catch { throw new RangeError('callbackUrl is not a valid URL'); } Type guard
const isParsableUrl = (v: unknown): v is string => { if (typeof v !== 'string') return false; try { new URL(v); return true; } catch { return false; } }; Try / catch
catch (e) { if (e?.details?.[0]?.description === 'callbackUrl is not a valid URL') { fix URL construction (scheme, whitespace) and re-submit } else throw e; } Prevention
- Always build URLs as `https://${host}${path}`
- new URL() the value client-side before every register call
- Validate config-sourced URLs at startup
When it happens
Trigger: callbackUrl lacking a scheme ('example.com/cb'), containing spaces or illegal characters, being a relative path ('/callback'), or interpolating 'null'/'undefined' into the string. Thrown during RegisterWebhook (and re-checked at delivery) entirely offline — no DNS query happens yet.
Common situations: Building the URL by concatenating a host without https://; template strings with undefined variables; copy-paste introducing whitespace; config values read as empty or undefined and stringified.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- callbackUrl is not allowed
- callbackUrl is required
- callbackUrl must use https
- callbackUrl hostname is a blocked metadata endpoint
- callbackUrl resolves to a private/reserved address: ${hostna
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/1e4bf7c6905b507e.
Report an issue: GitHub.