koala73/worldmonitor · error · Error
callbackUrl DNS resolution failed: ${message}
Error message
callbackUrl DNS resolution failed: ${message} What it means
assertCallbackUrlRegistrationSafe wraps any throw from the hostname resolver into 'callbackUrl DNS resolution failed: <inner message>'. The inner failures are the DoH HTTP error, the DoH RCODE error, or the AbortSignal.timeout(3000) abort when Cloudflare DoH does not answer within 3 seconds — so this message means the DNS lookup for the callback hostname could not be completed at registration time.
Source
Thrown at server/worldmonitor/shipping/v2/webhook-shared.ts:130
* 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);
return [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
}
export function generateSubscriberId(): string {
const bytes = new Uint8Array(12);
crypto.getRandomValues(bytes);
return 'wh_' + [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
}
View on GitHub (pinned to eeab0a219f)
Solutions
- Retry registration — resolver throws are frequently transient (timeout, rate limit)
- Confirm cloudflare-dns.com is reachable from the deployment (egress allowlist, proxy config)
- If the inner message is 'status 3', stop retrying: the hostname does not exist and needs a DNS fix, not a retry
Example fix
// before
await registerWebhook({ callbackUrl, chokepointIds }); // 400: callbackUrl DNS resolution failed: TimeoutError
// after
const msg = await tryRegister(callbackUrl, chokepointIds, /* attempts */ 3);
// inside tryRegister: catch, inspect inner message, retry only on timeout/HTTP errors Defensive patterns
Strategy: retry
Try / catch
catch (e) { const desc = e?.details?.[0]?.description ?? ''; if (/DNS resolution failed: (TimeoutError|DNS .* HTTP)/.test(desc)) { retry with backoff } else if (/status 3/.test(desc)) { fix DNS — hostname does not exist } else throw e; } Prevention
- Retry once or twice on resolver timeout — the DoH budget is only 3 seconds
- Pre-resolve the callback hostname yourself before bulk registrations
- Watch for the inner message: it decides retry vs DNS fix
When it happens
Trigger: Registering a webhook when the DoH fetch throws: 3-second timeout exceeded (slow or rate-limited resolver), TLS/network failure reaching cloudflare-dns.com, or the resolver endpoint blocked by egress policy. The outer wrap preserves the inner reason verbatim in the message.
Common situations: Cloudflare DoH latency or outage; bursty webhook registrations hitting resolver limits; hosting platforms with restricted egress; flaky network paths from the edge runtime to the resolver.
Understand the failure class
- DNS resolution errors: ENOTFOUND and getaddrinfo failures — how hostname lookups fail and how to debug them.
Related errors
- DNS ${recordType} lookup failed: HTTP ${response.status}
- DNS ${recordType} lookup failed: status ${data.Status}
- callbackUrl DNS resolution returned no addresses
- callbackUrl resolves to a private/reserved address
- COMPANY_MONITORING_CLASSIFICATION_FENCED
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/d5e4c5823d82e16e.
Report an issue: GitHub.