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
| Scenario | Intent | Checklist focus |
|---|---|---|
| Authorized transfer (this article) | You move the domain to a new registrar on purpose | Controlled unlock, auth code hygiene, verify completion, re-lock |
| Hijacking / unauthorized transfer | Attacker or insider moves control without approval | Detect unexpected lock removal, registrar drift, NS/SSL changes |
Phase 1 — Pre-Transfer Checklist
Do not request an auth code until every item below is green.
serverTransferProhibited 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.status andstatusAnalysis. ExpectclientTransferProhibited on production domains. NoteclientUpdateProhibited and registry lock services separately.WHOIS Signals to Read Before You Unlock
QueryGET /api/v1/whois?domain=example.com with Authorization: TOKEN=YOUR_API_KEY.
curl "https://whoisjson.com/api/v1/whois?domain=example.com" \
-H "Authorization: TOKEN=YOUR_API_KEY"| Field | Pre-transfer check |
|---|---|
status / statusAnalysis | Confirm transfer lock present; flagpendingTransfer,redemptionPeriod, holds |
registrar | Record losing registrar of record before unlock |
created / age | Estimate whether the ICANN 60-day lock may still apply |
expires / expiration | Avoid transferring domains inside an expiry cliff without renewal plan |
nameserver | Baseline delegation — should stay stable if DNS provider is unchanged |
dnssec | Note signed vs unsigned; DS updates may be needed at the new registrar |
Phase 2 — During the Transfer
- Remove
clientTransferProhibited(and related client locks if your registrar requires it) only for the approved window. - Request the auth code from the losing registrar. Prefer time-limited display and regenerate if unused within hours.
- Initiate transfer at the gaining registrar with the auth code. Do not reuse the code across domains.
- Watch WHOIS for
pendingTransferand registrar field changes. Poll on a schedule; enable Domain Monitoring for critical assets. - Approve or acknowledge any FOA / email confirmation required by the losing registrar — from a corporate mailbox, not a personal alias.
- 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.
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.
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 remove
clientTransferProhibitedon 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