Security

Domain Transfer Security Checklist: Locks, Auth Codes and WHOIS Signals

A production checklist for authorized domain transfers — unlock safely, verify EPP status in WHOIS JSON, protect the auth code, and re-lock after the gaining registrar takes control.

September 14, 202612 min readSecurity · Domain Transfer · EPP · WHOIS

Introduction

Domain transfers fail for mundane reasons and succeed for dangerous ones. A forgotten transfer lock blocks a planned migration. An unlocked domain with a leaked auth code becomes an unauthorized takeover. Security and ops teams need a repeatable process that treats transfers as change management — not as a one-click registrar form.

This guide is asecurity checklist for authorized transfers: what to verify before you unlock, which WHOIS/RDAP signals to read during the move, and what to re-baseline after the gaining registrar wins. It is intentionally narrower than the EPP status codes reference and distinct from domain hijacking detection (attack against domains you already own).

All programmatic checks use the WhoisJSON WHOIS API GET /api/v1/whois. Enrich with DNS and SSL only in the post-transfer phase.

Authorized Transfer vs Hijacking

ScenarioIntentChecklist focus
Authorized transfer (this article)You move the domain to a new registrar on purposeControlled unlock, auth code hygiene, verify completion, re-lock
Hijacking / unauthorized transferAttacker or insider moves control without approvalDetect unexpected lock removal, registrar drift, NS/SSL changes
Same signals, different playbook. Lock removal and registrar change appear in both workflows. The difference is change tickets, dual control, and expected windows versus unplanned alerts.

Phase 1 — Pre-Transfer Checklist

Do not request an auth code until every item below is green.

Confirm account ownership. Losing and gaining registrar accounts must be controlled by your org (MFA on, recovery email owned by the company, no shared passwords).
Check the 60-day transfer lock. ICANN policy appliesserverTransferProhibited for 60 days after a new registration or an incoming transfer. WHOIS will show it — you cannot force a client unlock past a server lock.
Inventory current locks. Readstatus andstatusAnalysis. ExpectclientTransferProhibited on production domains. NoteclientUpdateProhibited and registry lock services separately.
Snapshot the baseline. Store registrar name/id, nameservers, expiry, DNSSEC status, and SSL fingerprintbefore unlock so you can detect unexpected drift mid-transfer.
Plan DNS continuity. Prefer keeping the same DNS provider during the registrar move so NS records do not change in the same change window.
Open a change ticket. Document domain, losing/gaining registrar, unlock window, owners, and rollback (re-lock + dispute contacts).

WHOIS Signals to Read Before You Unlock

QueryGET /api/v1/whois?domain=example.com with Authorization: TOKEN=YOUR_API_KEY.

RequestcURL
curl "https://whoisjson.com/api/v1/whois?domain=example.com" \
  -H "Authorization: TOKEN=YOUR_API_KEY"
FieldPre-transfer check
status / statusAnalysisConfirm transfer lock present; flagpendingTransfer,redemptionPeriod, holds
registrarRecord losing registrar of record before unlock
created / ageEstimate whether the ICANN 60-day lock may still apply
expires / expirationAvoid transferring domains inside an expiry cliff without renewal plan
nameserverBaseline delegation — should stay stable if DNS provider is unchanged
dnssecNote signed vs unsigned; DS updates may be needed at the new registrar
Auth codes are not in WHOIS. EPP auth info (Auth-Code / EPP code) is issued by the losing registrar account. Treat it like a password: short TTL, out-of-band delivery, never commit to tickets or chat logs permanently.

Phase 2 — During the Transfer

  1. RemoveclientTransferProhibited (and related client locks if your registrar requires it) only for the approved window.
  2. Request the auth code from the losing registrar. Prefer time-limited display and regenerate if unused within hours.
  3. Initiate transfer at the gaining registrar with the auth code. Do not reuse the code across domains.
  4. Watch WHOIS forpendingTransfer and registrar field changes. Poll on a schedule; enable Domain Monitoring for critical assets.
  5. Approve or acknowledge any FOA / email confirmation required by the losing registrar — from a corporate mailbox, not a personal alias.
  6. If the transfer stalls, re-check server locks, unpaid invoices, and dispute holds before re-issuing auth codes.

Keep the unlock window short. Domains sitting unlocked overnight without an in-flight transfer are an unnecessary risk.

Phase 3 — Post-Transfer Hardening

Completion is not “registrar name changed.” Completion is control restored to a locked, monitored baseline.

Re-enable transfer lock

SetclientTransferProhibited (and update lock if policy requires) at the gaining registrar immediately.

Verify registrar of record

WHOISregistrar.name / registrar.id matches the gaining provider.

Confirm DNS unchanged

Compare live NS and A records to the pre-transfer baseline unless a DNS migration was planned.

Rotate secrets

Invalidate the used auth code path, review MFA on both registrar accounts, update inventory owner fields.

For unexpected NS or certificate changes in the same window, escalate using the hijacking playbook — authorized transfers should not silently rewrite DNS or TLS.

Python: Transfer Readiness Check

Gate the unlock step on WHOIS evidence: no pending states, no server transfer lock, and a recorded baseline.

transfer_readiness.pyPython
import requests

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

BLOCKING = (
    "pendingtransfer",
    "pendingdelete",
    "redemptionperiod",
    "serverhold",
    "clienthold",
)

def whois(domain: str) -> dict:
    r = requests.get(
        f"{BASE}/whois",
        params={"domain": domain},
        headers=HEADERS,
        timeout=20,
    )
    r.raise_for_status()
    return r.json()

def transfer_locked(data: dict) -> bool:
    analysis = data.get("statusAnalysis") or {}
    if "transferLocked" in analysis:
        return bool(analysis["transferLocked"])
    if analysis.get("clientTransferProhibited") or analysis.get("serverTransferProhibited"):
        return True
    status = [str(s).lower() for s in (data.get("status") or [])]
    return any("transferprohibited" in s for s in status)

def readiness(domain: str) -> dict:
    data = whois(domain)
    status = [str(s).lower() for s in (data.get("status") or [])]
    blockers = [s for s in status if any(b in s for b in BLOCKING)]
    locked = transfer_locked(data)
    server_lock = any("servertransferprohibited" in s for s in status)
    return {
        "domain": domain,
        "registrar": (data.get("registrar") or {}).get("name"),
        "transferLocked": locked,
        "serverTransferLock": server_lock,
        "blockers": blockers,
        "nameservers": data.get("nameserver") or [],
        "canUnlockSafely": locked and not server_lock and not blockers,
        "baseline": {
            "registrar": (data.get("registrar") or {}).get("name"),
            "nameservers": data.get("nameserver") or [],
            "expires": data.get("expires"),
            "dnssec": data.get("dnssec"),
        },
    }

print(readiness("example.com"))

Node.js: Post-Transfer Verification

After the gaining registrar reports success, confirm WHOIS registrar drift and that the transfer lock is back.

verify-transfer.jsNode.js
const API_KEY = "YOUR_API_KEY";
const HEADERS = { Authorization: `TOKEN=${API_KEY}` };

async function whois(domain) {
  const url = new URL("https://whoisjson.com/api/v1/whois");
  url.searchParams.set("domain", domain);
  const res = await fetch(url, { headers: HEADERS });
  if (!res.ok) throw new Error(`WHOIS failed: ${res.status}`);
  return res.json();
}

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

async function verifyTransfer(domain, expectedRegistrar, baselineNs = []) {
  const data = await whois(domain);
  const registrar = data.registrar?.name || null;
  const ns = [...(data.nameserver || [])]
    .map((v) => String(v).replace(/\.$/, "").toLowerCase())
    .sort();
  const expectedNs = [...baselineNs]
    .map((v) => String(v).replace(/\.$/, "").toLowerCase())
    .sort();
  const findings = [];

  if (expectedRegistrar && registrar !== expectedRegistrar) {
    findings.push("registrar_mismatch");
  }
  if (!transferLocked(data)) {
    findings.push("transfer_lock_not_restored");
  }
  if (expectedNs.length && JSON.stringify(ns) !== JSON.stringify(expectedNs)) {
    findings.push("nameserver_drift");
  }

  return {
    domain,
    registrar,
    transferLocked: transferLocked(data),
    findings,
    ok: findings.length === 0,
  };
}

verifyTransfer("example.com", "Example Registrar, Inc.", [
  "ns1.example-dns.com",
  "ns2.example-dns.com",
]).then(console.log);

Operational Policy Recommendations

  • Require dual approval to removeclientTransferProhibited on Tier 0 domains (login, payment, email)
  • Default state is locked; unlocked is an exception with an expiry time
  • Store pre/post WHOIS JSON snapshots in the change ticket for audit
  • Prefer registry lock / server-side locks for crown-jewel brands where available
  • Keep DNS provider migrations on a separate change ticket from registrar transfers

For portfolio-wide lock posture, combine this checklist with domain inventory and continuous monitoring for security teams.

Frequently Asked Questions

How do I know if a domain is transfer-locked?

Query GET /api/v1/whois and inspect status or statusAnalysis. clientTransferProhibited and serverTransferProhibited block registrar transfers. Prefer statusAnalysis.transferLocked when present.

Why does my transfer fail after I remove clientTransferProhibited?

A serverTransferProhibited lock may still apply — commonly the ICANN 60-day lock after registration or a prior transfer, or a paid registry lock. WHOIS will show the server status; your registrar cannot clear it unilaterally.

Is the Auth-Code visible in WHOIS?

No. Auth codes are issued through the losing registrar account. WHOIS/RDAP expose status, registrar and nameservers — not the EPP auth info secret.

When should I re-enable the transfer lock?

As soon as WHOIS shows the gaining registrar of record. Do not leave production domains unlocked after a successful transfer.

How is this different from hijacking detection?

This checklist governs planned, authorized moves. Hijacking detection alerts on unexpected lock removal, registrar changes and DNS/SSL drift without a change ticket.

Conclusion

Secure domain transfers are operational discipline: baseline WHOIS, respect server locks, handle auth codes like secrets, keep unlock windows short, and re-lock after the gaining registrar is confirmed. Use WhoisJSON to read transfer readiness and verify completion in JSON — then keep monitoring on for anything that was not in the ticket.

Verify transfer locks with WhoisJSON

Read EPP status, registrar and nameservers in one WHOIS/RDAP JSON response — before unlock and after completion.

Explore WHOIS APIDomain Monitoring
Secure Domain Transfers

Unlock Only When Ready — Re-Lock When Done

Use WHOIS EPP status and registrar signals to run authorized transfers without leaving production domains exposed.

Transfer lock checks60-day lock awarenessPre/post baselines1,000 free requests/month