Security

How to Detect Domain Hijacking with WHOIS, DNS and SSL Signals

Domain hijacking is a control-plane incident: unauthorized transfer, nameserver swap or certificate replacement. This playbook shows how to baseline and alert on the signals that still work when WHOIS contacts are redacted.

August 26, 202613 min readSecurity · Domain Hijacking · WHOIS · DNS · SSL

Introduction

Domain hijacking is not the same as phishing a lookalike brand or finding a newly registered typo. Hijacking means an attacker takes control of a domain you already own — or that your customers trust as yours — by transferring the registration, changing DNS delegation, or swapping the TLS certificate that terminates traffic.

Because GDPR and privacy proxies often hide registrant contacts, ownership identity is a weak detection surface. The reliable signals live in the control plane: EPP transfer locks, registrar of record, nameservers, live DNS answers and certificate fingerprints.

This guide builds a detection workflow on the WHOIS API, DNS lookup API and SSL certificate API. It complements brand protection (lookalikes) and phishing detection (adversary infrastructure), without replacing the EPP status reference.

Domain Hijacking vs Phishing vs Typosquatting

ThreatWhat changesPrimary detection focus
Domain hijackingControl of an existing domain (transfer, NS, cert)Baseline drift on your own assets
Typosquatting / brand abuseNew lookalike domains registered by othersSimilarity search + NRD + enrichment
Phishing infrastructureFresh domains built to impersonateAge, opacity, DNS/SSL weaponization signals
Scope of this article: protect domains already in your inventory. For lookalike discovery, see brand protection. For adversarial new domains, see phishing and newly registered domain guides.

Common Hijack Paths

  • Unauthorized registrar transfer after social engineering, account takeover or expired transfer lock
  • Nameserver rewrite at the registrar while the domain remains “registered to you” on paper
  • DNS record hijack (A/AAAA/CNAME) pointing traffic to attacker infrastructure
  • TLS certificate replacement so browsers still show a “valid” lock on a malicious origin
  • Post-expiry recovery races when redemption and pendingDelete windows are mishandled

Expiry lifecycle detail lives in what happens when a domain expires. Here the focus is detecting unauthorized control changes while the domain is still supposed to be yours.

Detection Signals That Still Work

When contacts are redacted, treat these fields as your hijack sensor set. Store a baseline, then compare every poll.

EPP transfer lock

clientTransferProhibited / serverTransferProhibited removed unexpectedly is a high-severity alert.

Registrar drift

registrar.name or registrar.id changing without a planned migration.

Nameserver drift

WHOIS nameserver list diverging from your approved DNS provider set.

Live DNS drift

NS, A/AAAA or critical CNAME answers changing outside a change window.

SSL fingerprint swap

details.fingerprint256 changing without a known renewal or ACME rotation.

Combined severity

Lock removal + NS change + cert change in the same window is almost always an incident.

Planned changes still look like hijacks. Maintain an allowlist calendar for registrar moves, DNS migrations and certificate renewals — or your SOC will page on every legitimate ACME renewal.

Build a Domain Control Baseline

Hijack detection is change detection. Without a baseline, every lookup is just a snapshot.

Baseline object to store per domainJSON
{
  "domain": "example.com",
  "capturedAt": "2026-08-26T10:00:00Z",
  "registrar": "Example Registrar, Inc.",
  "status": ["clientTransferProhibited", "clientUpdateProhibited"],
  "transferLocked": true,
  "nameservers": ["ns1.approved-dns.com", "ns2.approved-dns.com"],
  "aRecords": ["203.0.113.10"],
  "fingerprint256": "85:70:14:19:D2:F6:ED:7A:..."
}

Prefer RDAP-enriched statusAnalysis when present for lock interpretation, and keep raw status arrays for audit. See working with redacted WHOIS for why contact fields should not be part of the baseline identity model.

Query the Three Layers

Authenticate every call with Authorization: TOKEN=YOUR_API_KEY.

WHOIS — registrar, status, nameserverscURL
curl "https://whoisjson.com/api/v1/whois?domain=example.com" \
  -H "Authorization: TOKEN=YOUR_API_KEY"
DNS — live NS and A recordscURL
curl "https://whoisjson.com/api/v1/nslookup?domain=example.com" \
  -H "Authorization: TOKEN=YOUR_API_KEY"
SSL — fingerprint and issuercURL
curl "https://whoisjson.com/api/v1/ssl-cert-check?domain=example.com" \
  -H "Authorization: TOKEN=YOUR_API_KEY"

A minimum viable check is three requests per domain. Critical customer-facing hostnames may need additional SSL checks for code www | or login subdomains.

Severity Model

FindingSuggested severityTypical response
Transfer lock removed onlyHighVerify registrar account MFA and recent auth events immediately
Registrar changedCriticalIncident bridge; confirm whether a transfer was authorized
WHOIS NS changedCriticalFreeze DNS changes; compare with live NS lookup
Live A/AAAA changedHigh / CriticalCheck CDN cutovers vs malicious hosting; review access logs
SSL fingerprint changedMedium / HighCorrelate with ACME renewals; escalate if paired with DNS drift
Lock + NS + cert in same pollCriticalAssume compromise until proven otherwise

Python Detection Pipeline

Compare a stored baseline with live WHOIS, DNS and SSL responses and emit structured findings.

detect_hijack.pyPython
import requests

API_KEY = "YOUR_API_KEY"
HEADERS = {"Authorization": f"TOKEN={API_KEY}"}
BASE = "https://whoisjson.com/api/v1"

def get_json(path, domain):
    r = requests.get(
        f"{BASE}/{path}",
        params={"domain": domain},
        headers=HEADERS,
        timeout=30,
    )
    r.raise_for_status()
    return r.json()

def norm_ns(values):
    return sorted({(v or "").rstrip(".").lower() for v in (values or []) if v})

def transfer_locked(status, analysis):
    analysis = analysis or {}
    if "transferLocked" in analysis:
        return bool(analysis["transferLocked"])
    status = [s.lower() for s in (status or [])]
    return any("transferprohibited" in s for s in status)

def detect_hijack(domain, baseline):
    whois = get_json("whois", domain)
    dns = get_json("nslookup", domain)
    ssl = get_json("ssl-cert-check", domain)

    findings = []
    registrar = (whois.get("registrar") or {}).get("name")
    locked = transfer_locked(whois.get("status"), whois.get("statusAnalysis"))
    whois_ns = norm_ns(whois.get("nameserver"))
    live_ns = norm_ns(dns.get("NS"))
    live_a = sorted(dns.get("A") or [])
    fp = (ssl.get("details") or {}).get("fingerprint256")

    if baseline.get("transferLocked") and not locked:
        findings.append({"signal": "transfer_lock_removed", "severity": "high"})
    if registrar and registrar != baseline.get("registrar"):
        findings.append({
            "signal": "registrar_changed",
            "severity": "critical",
            "from": baseline.get("registrar"),
            "to": registrar,
        })
    if whois_ns and whois_ns != norm_ns(baseline.get("nameservers")):
        findings.append({"signal": "whois_nameserver_changed", "severity": "critical"})
    if live_ns and whois_ns and live_ns != whois_ns:
        findings.append({"signal": "whois_vs_live_ns_mismatch", "severity": "high"})
    if live_a and live_a != sorted(baseline.get("aRecords") or []):
        findings.append({"signal": "a_record_changed", "severity": "high"})
    if fp and fp != baseline.get("fingerprint256"):
        findings.append({"signal": "ssl_fingerprint_changed", "severity": "medium"})

    critical = sum(1 for f in findings if f["severity"] == "critical")
    if critical >= 2:
        findings.append({"signal": "multi_signal_compromise", "severity": "critical"})

    return {
        "domain": domain,
        "findings": findings,
        "alert": any(f["severity"] in ("high", "critical") for f in findings),
    }

baseline = {
    "registrar": "Example Registrar, Inc.",
    "transferLocked": True,
    "nameservers": ["ns1.approved-dns.com", "ns2.approved-dns.com"],
    "aRecords": ["203.0.113.10"],
    "fingerprint256": "85:70:14:19:D2:F6:ED:7A:...",
}
print(detect_hijack("example.com", baseline))

Node.js Detection Pipeline

Same three-layer comparison using native code fetch | .

detect-hijack.jsNode.js
const API_KEY = "YOUR_API_KEY";
const HEADERS = { Authorization: `TOKEN=${API_KEY}` };
const BASE = "https://whoisjson.com/api/v1";

async function getJson(path, domain) {
  const url = new URL(`${BASE}/${path}`);
  url.searchParams.set("domain", domain);
  const res = await fetch(url, { headers: HEADERS });
  if (!res.ok) throw new Error(`${path} failed: ${res.status}`);
  return res.json();
}

const normNs = (values = []) =>
  [...new Set(values.filter(Boolean).map((v) => String(v).replace(/\.$/, "").toLower()))].sort();

function transferLocked(status = [], analysis = {}) {
  if (Object.prototype.hasOwnProperty.call(analysis, "transferLocked")) {
    return Boolean(analysis.transferLocked);
  }
  return status.some((s) => String(s).toLowerCase().includes("transferprohibited"));
}

async function detectHijack(domain, baseline) {
  const [whois, dns, ssl] = await Promise.all([
    getJson("whois", domain),
    getJson("nslookup", domain),
    getJson("ssl-cert-check", domain),
  ]);

  const findings = [];
  const registrar = whois.registrar?.name || null;
  const locked = transferLocked(whois.status, whois.statusAnalysis);
  const whoisNs = normNs(whois.nameserver);
  const liveNs = normNs(dns.NS);
  const liveA = [...(dns.A || [])].sort();
  const fp = ssl.details?.fingerprint256 || null;

  if (baseline.transferLocked && !locked) {
    findings.push({ signal: "transfer_lock_removed", severity: "high" });
  }
  if (registrar && registrar !== baseline.registrar) {
    findings.push({
      signal: "registrar_changed",
      severity: "critical",
      from: baseline.registrar,
      to: registrar,
    });
  }
  if (whoisNs.length && JSON.stringify(whoisNs) !== JSON.stringify(normNs(baseline.nameservers))) {
    findings.push({ signal: "whois_nameserver_changed", severity: "critical" });
  }
  if (liveNs.length && whoisNs.length && JSON.stringify(liveNs) !== JSON.stringify(whoisNs)) {
    findings.push({ signal: "whois_vs_live_ns_mismatch", severity: "high" });
  }
  if (liveA.length && JSON.stringify(liveA) !== JSON.stringify([...(baseline.aRecords || [])].sort())) {
    findings.push({ signal: "a_record_changed", severity: "high" });
  }
  if (fp && fp !== baseline.fingerprint256) {
    findings.push({ signal: "ssl_fingerprint_changed", severity: "medium" });
  }
  if (findings.filter((f) => f.severity === "critical").length >= 2) {
    findings.push({ signal: "multi_signal_compromise", severity: "critical" });
  }

  return {
    domain,
    findings,
    alert: findings.some((f) => f.severity === "high" || f.severity === "critical"),
  };
}

detectHijack("example.com", {
  registrar: "Example Registrar, Inc.",
  transferLocked: true,
  nameservers: ["ns1.approved-dns.com", "ns2.approved-dns.com"],
  aRecords: ["203.0.113.10"],
  fingerprint256: "85:70:14:19:D2:F6:ED:7A:...",
}).then(console.log);

Continuous Monitoring Instead of Polling Alone

Homegrown polls work for a short list. For production portfolios, use Domain Monitoring to watch WHOIS, DNS and SSL changes and receive email alerts when something moves. Keep custom severity logic for the domains where a false negative is unacceptable — payment hosts, SSO, API gateways, marketing apex domains.

Related operational guides: domain monitoring baseline for security teams, nameserver monitoring and SSL certificate monitoring.

Incident Response Checklist

Contain DNS first. If live A/NS already moved, switch traffic via registrar emergency contacts or secondary DNS only if you still control the account.
Re-enable transfer locks. RestoreclientTransferProhibited (and update locks where appropriate) after regaining registrar access.
Rotate credentials. Registrar account, DNS provider, ACME account keys and any shared vault secrets used for renewals.
Re-issue certificates deliberately. After DNS is trusted again, rotate TLS and update your storedfingerprint256 baseline.
Preserve evidence. Save WHOIS/DNS/SSL JSON snapshots with timestamps for IR and legal follow-up.

Limits and False Positives

  • CDN cutovers and blue/green IP changes look like A-record hijacks without a change window
  • Let’s Encrypt and other ACME renewals change fingerprints on a short cadence
  • Registrar rebrands or IANA ID remaps can appear as registrar drift
  • WHOIS NS and live NS can diverge briefly during DNS provider migrations
  • Redacted contacts will not tell you who initiated a transfer — escalate to the registrar
Production tip: Score multi-signal clusters higher than single diffs. A lone fingerprint change is often renewal. Fingerprint + NS + lock removal in one hour is an incident.

Frequently Asked Questions

How do you detect domain hijacking with an API?

Baseline registrar, EPP transfer locks, nameservers, live DNS records and SSL fingerprints. Poll WHOIS, DNS and SSL endpoints, then alert on unauthorized drift — especially multi-signal changes.

Which WhoisJSON endpoints are used for hijack detection?

Use GET /api/v1/whois for registrar and status, GET /api/v1/nslookup for live DNS, and GET /api/v1/ssl-cert-check for certificate fingerprints. Domain Monitoring can alert on changes continuously.

Is removal of clientTransferProhibited always a hijack?

No. Administrators remove transfer locks before legitimate transfers. Treat unexpected removal as high severity and verify against change tickets and registrar account activity.

Can domain hijacking be detected when WHOIS contacts are redacted?

Yes. Hijack detection relies on control-plane fields — locks, registrar, nameservers, DNS and SSL — not registrant email or phone.

How is hijacking different from phishing detection?

Hijacking watches your existing domains for unauthorized control changes. Phishing detection scores someone else’s newly registered domains for weaponization risk.

How often should hijack checks run?

Critical domains often need hourly or continuous monitoring. Broader portfolio scans can run daily. Increase frequency during registrar migrations or after account security events.

Conclusion

Domain hijacking detection is baseline discipline: lock state, registrar, nameservers, live DNS and certificate fingerprints. Query WhoisJSON’s WHOIS, DNS and SSL APIs on a schedule, escalate multi-signal drift immediately, and keep Domain Monitoring on the assets you cannot afford to miss.

Detect domain control changes with WhoisJSON

Monitor WHOIS, DNS and SSL in JSON — then alert before a hijack becomes an outage or brand incident.

Start Domain MonitoringExplore WHOIS API
Domain Hijacking Detection

Catch Unauthorized Domain Control Changes Early

Use WHOIS locks, DNS drift and SSL fingerprint swaps to detect hijacks — even when registrant contacts are redacted.

Transfer lock alertsNS & registrar driftSSL fingerprint checks1,000 free requests/month