koala73/worldmonitor · error · Error
DNS ${recordType} lookup failed: status ${data.Status}
Error message
DNS ${recordType} lookup failed: status ${data.Status} What it means
The Cloudflare DoH JSON response's Status field is the DNS RCODE; anything other than 0 makes resolveRecordType throw (2=SERVFAIL, 3=NXDOMAIN), which registration later wraps into 'callbackUrl DNS resolution failed: DNS {A|AAAA} lookup failed: status {rcode}'. It means the resolver authoritatively answered that the callback hostname cannot be resolved.
Source
Thrown at server/worldmonitor/shipping/v2/webhook-shared.ts:101
async function defaultResolveHostname(hostname: string): Promise<string[]> {
const resolveHostnameForTest = getResolveHostnameForTest();
if (resolveHostnameForTest) return resolveHostnameForTest(hostname);
const resolveRecordType = async (recordType: 'A' | 'AAAA'): Promise<string[]> => {
const url = new URL(DNS_JSON_ENDPOINT);
url.searchParams.set('name', hostname);
url.searchParams.set('type', recordType);
const response = await fetch(url, {
headers: {
Accept: 'application/dns-json',
'User-Agent': 'WorldMonitor-ShippingV2-Webhooks/1.0',
},
signal: AbortSignal.timeout(DNS_RESOLUTION_TIMEOUT_MS),
});
if (!response.ok) throw new Error(`DNS ${recordType} lookup failed: HTTP ${response.status}`);
const data = await response.json() as { Status?: number; Answer?: Array<{ type?: number; data?: string }> };
if (data.Status !== 0) throw new Error(`DNS ${recordType} lookup failed: status ${data.Status}`);
const expectedType = recordType === 'A' ? 1 : 28;
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> {View on GitHub (pinned to eeab0a219f)
Solutions
- Verify the hostname resolves on the public internet: dig +short example.com A or a DoH lookup in a browser
- Fix the typo or create the missing A/AAAA record at your DNS provider and wait for propagation
- If the host only exists internally, switch to a public hostname — the SSRF policy would block a private target anyway
Example fix
// before
await registerWebhook({ callbackUrl: 'https://hooks.corp.internal/cb', chokepointIds });
// after
await registerWebhook({ callbackUrl: 'https://hooks.example.com/cb', chokepointIds }); // publicly resolvable Defensive patterns
Strategy: validation
Validate before calling
// pre-resolve before registering
const r = await fetch(`https://cloudflare-dns.com/dns-query?name=${host}&type=A`, { headers: { Accept: 'application/dns-json' } });
if ((await r.json()).Status !== 0) fail early — fix DNS before the RPC; Try / catch
catch (e) { if (/status 3/.test(e?.details?.[0]?.description ?? '')) { stop retrying — hostname does not exist; fix DNS } else throw e; } Prevention
- Verify the callback hostname resolves publicly (dig A/AAAA) before registering
- Never use internal-only hostnames as public callback URLs
- Re-check DNS after domain migrations or record deletions
When it happens
Trigger: Registering a webhook whose hostname does not exist publicly (status 3 NXDOMAIN) or whose authoritative nameservers are failing (status 2 SERVFAIL): typo'd domain, recently deleted DNS record, or a split-horizon name that only exists inside a private network.
Common situations: Staging domain never created in public DNS; internal hostname (hooks.corp.internal) used as a public callback URL; DNS record removed after copying an old config; nameserver misconfiguration at the registrar.
Related errors
- DNS ${recordType} lookup failed: HTTP ${response.status}
- callbackUrl DNS resolution failed: ${message}
- callbackUrl DNS resolution returned no addresses
- callbackUrl resolves to a private/reserved address
- API key required
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/1377f47547b71abc.
Report an issue: GitHub.