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

  1. Retry registration — resolver throws are frequently transient (timeout, rate limit)
  2. Confirm cloudflare-dns.com is reachable from the deployment (egress allowlist, proxy config)
  3. 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

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

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/d5e4c5823d82e16e. Report an issue: GitHub.