{"record":{"id":"d5e4c5823d82e16e","repo":"koala73/worldmonitor","slug":"callbackurl-dns-resolution-failed-message","errorCode":null,"errorMessage":"callbackUrl DNS resolution failed: ${message}","messagePattern":"callbackUrl DNS resolution failed: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/shipping/v2/webhook-shared.ts","lineNumber":130,"sourceCode":" * Validate the current DNS answer before storing a webhook. Delivery makes the\n * same check immediately before send and pins the resulting socket, which\n * keeps this fail-fast check from becoming the only SSRF control.\n */\nexport async function assertCallbackUrlRegistrationSafe(\n  callbackUrl: string,\n  resolveHostname: ResolveHostname = defaultResolveHostname,\n): Promise<void> {\n  const staticError = isBlockedCallbackUrl(callbackUrl);\n  if (staticError) throw new Error(staticError);\n\n  const hostname = new URL(callbackUrl).hostname.toLowerCase();\n  if (isIpLiteral(hostname)) return;\n  let resolvedAddresses: string[];\n  try {\n    resolvedAddresses = await resolveHostname(hostname);\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error);\n    throw new Error(`callbackUrl DNS resolution failed: ${message}`);\n  }\n  if (!resolvedAddresses.length) throw new Error('callbackUrl DNS resolution returned no addresses');\n  const blocked = resolvedAddresses.find(isBlockedResolvedAddress);\n  if (blocked) throw new Error('callbackUrl resolves to a private/reserved address');\n}\n\nexport async function generateSecret(): Promise<string> {\n  const bytes = new Uint8Array(32);\n  crypto.getRandomValues(bytes);\n  return [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport function generateSubscriberId(): string {\n  const bytes = new Uint8Array(12);\n  crypto.getRandomValues(bytes);\n  return 'wh_' + [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');\n}\n","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/server/worldmonitor/shipping/v2/webhook-shared.ts#L112-L148","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait registerWebhook({ callbackUrl, chokepointIds }); // 400: callbackUrl DNS resolution failed: TimeoutError\n// after\nconst msg = await tryRegister(callbackUrl, chokepointIds, /* attempts */ 3);\n// inside tryRegister: catch, inspect inner message, retry only on timeout/HTTP errors","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["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"],"tags":["dns","timeout","doh","network","webhooks","registration"],"backgroundTag":"dns-lookup-failed","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"}