{"record":{"id":"2ab0d16b7d47b0c6","repo":"koala73/worldmonitor","slug":"dns-recordtype-lookup-failed-http-response-s-2ab0d1","errorCode":null,"errorMessage":"DNS ${recordType} lookup failed: HTTP ${response.status}","messagePattern":"DNS (.+?) lookup failed: HTTP (.+?)","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/shipping/v2/webhook-shared.ts","lineNumber":99,"sourceCode":"  return null;\n}\n\nasync function defaultResolveHostname(hostname: string): Promise<string[]> {\n  const resolveHostnameForTest = getResolveHostnameForTest();\n  if (resolveHostnameForTest) return resolveHostnameForTest(hostname);\n\n  const resolveRecordType = async (recordType: 'A' | 'AAAA'): Promise<string[]> => {\n    const url = new URL(DNS_JSON_ENDPOINT);\n    url.searchParams.set('name', hostname);\n    url.searchParams.set('type', recordType);\n    const response = await fetch(url, {\n      headers: {\n        Accept: 'application/dns-json',\n        'User-Agent': 'WorldMonitor-ShippingV2-Webhooks/1.0',\n      },\n      signal: AbortSignal.timeout(DNS_RESOLUTION_TIMEOUT_MS),\n    });\n    if (!response.ok) throw new Error(`DNS ${recordType} lookup failed: HTTP ${response.status}`);\n    const data = await response.json() as { Status?: number; Answer?: Array<{ type?: number; data?: string }> };\n    if (data.Status !== 0) throw new Error(`DNS ${recordType} lookup failed: status ${data.Status}`);\n    const expectedType = recordType === 'A' ? 1 : 28;\n    return (data.Answer ?? [])\n      .filter(answer => answer.type === expectedType && typeof answer.data === 'string')\n      .map(answer => answer.data!);\n  };\n  const records = await Promise.all([resolveRecordType('A'), resolveRecordType('AAAA')]);\n  return records.flat();\n}\n\n/**\n * 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,","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/server/worldmonitor/shipping/v2/webhook-shared.ts#L81-L117","documentation":"The webhook registration DNS resolver queries Cloudflare DoH (https://cloudflare-dns.com/dns-query with Accept: application/dns-json) for A and AAAA in parallel under a 3s AbortSignal timeout; a non-2xx HTTP response from the resolver throws this error, which assertCallbackUrlRegistrationSafe then wraps as 'callbackUrl DNS resolution failed: ...'. It means the DoH service itself failed or rejected the request — not that the hostname is invalid.","triggerScenarios":"Registering a webhook when Cloudflare DoH returns 429 (rate-limited) or 5xx; egress firewall/NAT/proxy blocking cloudflare-dns.com from the server runtime; a hostname malformed badly enough that DoH answers 400.","commonSituations":"A burst of RegisterWebhook calls tripping the DoH rate limit; restrictive egress rules in the hosting platform; a regional Cloudflare incident; corporate proxies intercepting the resolver.","solutions":["Retry registration after a short backoff — DoH HTTP failures are usually transient rate limits or blips","Verify the server runtime can reach https://cloudflare-dns.com/dns-query (egress rules, proxies)","If deploying where DoH is unreachable, allowlist the endpoint — the resolver is not configurable outside the test Symbol override"],"exampleFix":"// before\nawait registerWebhook({ callbackUrl, chokepointIds }); // 400: DNS A lookup failed: HTTP 429\n// after\nawait withRetry(() => registerWebhook({ callbackUrl, chokepointIds }), { attempts: 3, baseMs: 750 });","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (e) { const desc = e?.details?.[0]?.description ?? ''; if (/DNS .* lookup failed: HTTP/.test(desc)) { retry registration with backoff — DoH HTTP failures are transient } else throw e; }","preventionTips":["Retry webhook registration on DNS HTTP failures with exponential backoff","Space out bulk registrations to avoid tripping the Cloudflare DoH rate limit","Ensure the server runtime's egress allows cloudflare-dns.com"],"tags":["dns","doh","cloudflare","network","webhooks","ssrf","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"}