Engineering

How to Query WHOIS Data in Node.js Using a REST API (2026 Guide)

March 9, 20268 min readNode.js · WHOIS · REST API

Introduction

Domain data is a critical signal across a wide range of applications. Security teams use it to detect phishing infrastructure and typosquatting campaigns the moment a suspicious domain is registered. SaaS platforms validate domain ownership at signup. Monitoring tools track registrar and DNS changes before they cause outages. In all these scenarios, the need is the same: programmatic, structured access to WHOIS data from Node.js.

The legacy WHOIS protocol (TCP port 43) returns unstructured plain text that varies by registrar, making it unreliable for production systems. A WHOIS REST API solves this by normalizing the output into consistent JSON, handling TLD routing, and absorbing GDPR-driven privacy redactions transparently. This guide shows you exactly how to integrate the WhoisJSON WHOIS API into a Node.js application — from a single lookup to a real-world phishing detection workflow.

What is a WHOIS API?

Raw WHOIS queries hit a registrar-operated server on port 43 and return a freeform text block. There is no standard schema — the same field can be named Registrant Name, registrant-name, or reg-name depending on the registry. Parsing this reliably across 1,500+ TLDs requires maintaining hundreds of individual parsers, and the protocol offers no authentication, rate limiting feedback, or structured error codes.

A WHOIS API wraps that complexity behind a single HTTP endpoint. You send a GET request with your domain and API key; you get back a structured JSON object with predictable field names — registrar, created, expires, nameserver, contacts, and more. The API handles TLD routing, caching, and error normalization so your application code stays clean.

RDAP note: RDAP (Registration Data Access Protocol) is the modern successor to WHOIS. The WhoisJSON API uses RDAP automatically when available for a given TLD, and falls back to legacy WHOIS otherwise — giving you the best data without any configuration changes.

Prerequisites

  • Node.js 18+ — this guide uses the native fetch API (no external HTTP library required).
  • A free WhoisJSON API key sign up here for 1,000 free requests per month. No credit card required.
  • Basic familiarity with async/await.

Basic WHOIS Lookup in Node.js

The simplest integration is a single fetch call with your API key in the Authorization header. Every WhoisJSON endpoint shares the same token and the same base URL pattern.

whois-lookup.jsJavaScript
const API_KEY = 'YOUR_API_KEY'; // from your dashboard
const domain  = 'example.com';

const response = await fetch(
  `https://whoisjson.com/api/v1/whois?domain=${domain}`,
  {
    headers: { 'Authorization': `Token=${API_KEY}` }
  }
);

if (!response.ok) {
  throw new Error(`HTTP error: ${response.status}`);
}

const data = await response.json();
console.log(data);

The API returns a normalized JSON object. Here is a representative excerpt of a real response — notice the pre-computed helper objects that save you from manual date arithmetic:

Response (JSON)JSON
{
  "name":       "example.com",
  "registered": true,
  "source":     "rdap",
  "created":    "1995-08-14",
  "changed":    "2024-08-13",
  "expires":    "2026-08-13",

  "age": {
    "days":              10800,
    "years":             29,
    "isNewlyRegistered": false,
    "isYoung":           false
  },

  "expiration": {
    "daysLeft":       148,
    "isExpiringSoon": false,
    "isExpired":      false
  },

  "registrar": {
    "id":    "292",
    "name":  "MarkMonitor Inc.",
    "email": "abusecomplaints@markmonitor.com"
  },

  "nameserver": [
    "a.iana-servers.net",
    "b.iana-servers.net"
  ],

  "statusAnalysis": {
    "clientDeleteProhibited":   true,
    "clientTransferProhibited": true,
    "clientUpdateProhibited":   true,
    "isActive":                 true,
    "isPendingDelete":          false
  },

  "nsAnalysis": {
    "count":                2,
    "singleInfrastructure": true,
    "mixedInfrastructure":  false,
    "selfHosted":           false
  },

  "contacts": {
    "owner": [{ "name": "REDACTED FOR PRIVACY", "country": "US" }],
    "admin": [],
    "tech":  []
  },

  "dnssec":        "signedDelegation",
  "parsedContacts": true
}

Parsing the Response

The response ships with pre-computed helper objects so you rarely need to do date arithmetic yourself. age tells you how old the domain is; expiration gives you the days remaining and expiry flags; statusAnalysis breaks down the EPP status codes into readable booleans.

parse-whois.jsJavaScript
const {
  registrar, created, expires, nameserver,
  registered, age, expiration, statusAnalysis
} = data;

if (!registered) {
  console.log('Domain is not registered.');
} else {
  console.log(`Registrar    : ${registrar.name}`);
  console.log(`Created      : ${created} (${age.years} years old)`);
  console.log(`Expires      : ${expires}`);
  console.log(`Days left    : ${expiration.daysLeft}`);
  console.log(`Expiring soon: ${expiration.isExpiringSoon}`);
  console.log(`Nameservers  : ${nameserver.join(', ')}`);
  console.log(`Transfer lock: ${statusAnalysis.clientTransferProhibited}`);
  console.log(`Pending del. : ${statusAnalysis.isPendingDelete}`);
}

The registered boolean is the canonical availability check. Always inspect it before accessing date or registrar fields — unregistered domains return registered: false with null or empty sub-objects. The source field ( "rdap" or "whois") tells you which protocol was used to fetch the data.

Bulk Domain Lookup

When you need to process a list of domains — portfolio expiry scans, competitor analysis, or newly registered domain checks — loop over the array with a small delay between requests. This keeps your application within the API rate limits and avoids dropped requests under load.

bulk-whois.jsJavaScript
const domains = ['example.com', 'github.com', 'openai.com'];

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function bulkWhoisLookup(apiKey, domains) {
  const results = [];

  for (const domain of domains) {
    const res = await fetch(
      `https://whoisjson.com/api/v1/whois?domain=${domain}`,
      { headers: { 'Authorization': `Token=${apiKey}` } }
    );
    const data = await res.json();
    results.push({ domain, data });

    await delay(300); // 300 ms gap — stay within rate limits
  }

  return results;
}

const results = await bulkWhoisLookup('YOUR_API_KEY', domains);
console.log(JSON.stringify(results, null, 2));
Pro tip: On paid plans you can parallelise requests in small batches using Promise.all. Pair it with exponential backoff on 429 responses for a fully production-resilient implementation.

Error Handling and Edge Cases

Production code must handle four common failure modes: unregistered domains, unsupported TLDs, rate limiting, and network timeouts. The snippet below wraps all of these in a single utility function using AbortController for deadline management.

safe-lookup.jsJavaScript
async function safeWhoisLookup(apiKey, domain) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 5000);

  try {
    const res = await fetch(
      `https://whoisjson.com/api/v1/whois?domain=${domain}`,
      {
        headers: { 'Authorization': `Token=${apiKey}` },
        signal: controller.signal
      }
    );
    clearTimeout(timer);

    if (res.status === 404) return { error: 'Domain not found' };
    if (res.status === 422) return { error: 'TLD not supported' };
    if (res.status === 429) return { error: 'Rate limit hit — reduce request frequency' };
    if (!res.ok)            return { error: `API error ${res.status}` };

    const data = await res.json();
    if (!data.registered)   return { domain, registered: false };

    return { domain, registered: true, data };

  } catch (err) {
    clearTimeout(timer);
    if (err.name === 'AbortError') return { error: 'Request timed out (5s)' };
    return { error: err.message };
  }
}

Four edge cases to keep in mind:

  • Unregistered domains return { "registered": false } — not a 404. Always check the boolean, not the HTTP status.
  • Unsupported TLDs return a 422 status. Maintain a local blocklist to avoid burning quota on known-unsupported extensions.
  • Privacy-redacted fields (GDPR) return "REDACTED FOR PRIVACY" strings — do not treat these as valid contact data or missing fields.
  • Network timeouts are rare but real for obscure TLDs with slow WHOIS servers. A 5-second AbortController deadline with a single retry is a safe default.

Real-World Use Case: Detect Recently Registered Domains

Studies consistently show that a high percentage of phishing domains are weaponised within their first 30 days. Instead of computing domain age manually, the API exposes age.isNewlyRegistered and age.isYoung directly. The snippet below builds a composite risk score from these flags combined with EPP status analysis — a practical first filter for a threat intelligence pipeline.

domain-risk-score.jsJavaScript
async function domainRiskScore(apiKey, domain) {
  const res = await fetch(
    `https://whoisjson.com/api/v1/whois?domain=${domain}`,
    { headers: { 'Authorization': `Token=${apiKey}` } }
  );
  const data = await res.json();

  if (!data.registered) return { domain, risk: 0, reason: 'unregistered' };

  const { age, expiration, statusAnalysis, registrar } = data;

  // age.isNewlyRegistered = registered within the last 30 days
  // age.isYoung           = registered within the last 6 months
  let score  = 0;
  const flags = [];

  if (age.isNewlyRegistered)                     { score += 3; flags.push('newly registered'); }
  else if (age.isYoung)                          { score += 1; flags.push('young domain'); }
  if (!statusAnalysis.clientTransferProhibited)  { score += 1; flags.push('no transfer lock'); }
  if (statusAnalysis.isPendingDelete)            { score += 2; flags.push('pending deletion'); }
  if (expiration.isExpiringSoon)                 { score += 1; flags.push('expiring soon'); }

  return {
    domain,
    ageDays:   age.days,
    registrar: registrar.name,
    risk:      score,
    flags
  };
}

// Scan a batch of suspicious domains
const suspects = [
  'paypal-secure-login.com',
  'amazon-account-verify.net',
  'google-security-alert.org'
];

const delay = (ms) => new Promise((r) => setTimeout(r, ms));

(async () => {
  for (const domain of suspects) {
    const result = await domainRiskScore('YOUR_API_KEY', domain);
    const label  = result.risk >= 3 ? 'HIGH RISK'
                 : result.risk >= 1 ? 'MEDIUM RISK'
                 :                    'LOW RISK';
    console.log(`[${label}] ${domain} — score: ${result.risk}, flags: ${result.flags.join(', ') || 'none'}`);
    await delay(300);
  }
})();

You can extend this further by cross-referencing the nsAnalysis.mixedInfrastructure flag (nameservers split across providers — common in hijacking scenarios) and SSL certificate issuance date from the WhoisJSON SSL API. Domains that are newly registered, unprotected by EPP locks, and carrying a certificate issued within the last 48 hours represent the strongest composite phishing signal.

Conclusion

Querying WHOIS data in Node.js is straightforward once you replace the raw protocol with a REST API. The native fetch API, a single Authorization header, and the patterns covered in this guide are all you need to build production-grade domain intelligence into your application.

Four things to take away:

  • Always check registered: false before accessing date or registrar fields.
  • Add a 300 ms delay between iterations in bulk loops to stay within rate limits.
  • Use AbortController to enforce a request deadline in production code.
  • Domain registration age is a high-signal, low-cost indicator for threat detection.

Ready to Build?

Everything in this guide runs on the free tier. Sign up, grab your key, and make your first API call in under two minutes.