Introduction
In 2026, the window between a domain registration and the first phishing hit has shrunk to a matter of hours. A threat actor registers yourbrand-secure-login.com at 09:00, provisions a Let's Encrypt certificate by 09:10, and begins credential harvesting before your security team finishes their morning standup. The damage — stolen credentials, eroded customer trust, regulatory exposure — is done before most organizations even know the domain exists.
The root cause is timing. Traditional brand protection workflows rely on reactive processes: a customer reports a suspicious email, a support ticket surfaces a lookalike URL, someone notices a phishing alert in a threat feed. Each of these detection paths has a lag measured in days. A modern brand protection domain monitoring system needs to flip that model — it should detect unauthorized registrations the moment they appear, not after the attack is already underway.
This guide walks through building a fully automated pipeline using four API signals — domain availability, WHOIS registration data, DNS records, and SSL certificates. You will get a Python implementation that generates brand variants, enriches each one with live data, scores them by risk, and triggers alerts on high-confidence threats. The complete working script is at the end.
What You Are Actually Monitoring For
Brand domain monitoring brand protection is not one problem — it is four distinct attack vectors, each requiring a different generation strategy and a different set of detection signals.
Typosquatting
Character-level mutations of your brand name: transpositions ( youbrand vs yourbrand), doubled characters (yourrband), omissions (yurbrand), adjacent-key substitutions (youeband). These are the oldest and most common attack vector — easy to generate programmatically and easy to automate registration for.
Keyword combinations
Domains that append or prepend trust-signalling words to your brand name: yourbrand-login.com, yourbrand-support.net, secure-yourbrand.com, yourbrand-account-verify.com. These are particularly dangerous for consumer brands because they exploit user intent — the target is actively looking for a legitimate login page.
TLD variants
The .com/.net/.io/.co/.org/.eu equivalents of your primary domain. A competitor or threat actor registering yourbrand.io or yourbrand.co may not constitute abuse today — but their presence on a watchlist ensures you are alerted the moment DNS or SSL activity suggests weaponization.
Homoglyphs and IDN domains
Unicode characters that are visually identical or nearly identical to ASCII letters: Cyrillic а (U+0430) for Latin a, Greek ο (U+03BF) for Latin o, or the classic zero-for-O swap. These IDN homoglyph attacks are particularly effective in contexts where the full domain is not displayed — mobile apps, shortened links, QR codes. For a deeper look at the phishing signals that correlate with these registrations, see the phishing domain detection guide.
The Four API Signals That Matter
Four endpoints, used in combination, give you everything you need to triage a suspected brand domain with confidence. Each answers a different question.
Domain Availability
Is the variant already registered? A false on the available field is the entry condition for all downstream checks.
WHOIS
Who registered it and when? age.isNewlyRegistered (under 30 days) is a strong risk indicator. Fully redacted contacts mean a privacy-shielded registrant — common in fraudulent registrations. See the WHOIS API JSON field reference for the full field list.
DNS
Is the domain armed or dormant? An active A record means it resolves. An MX record means it can send email — the most dangerous configuration for brand impersonation. No DMARC means your brand's email security policy is not enforced. Details in the DNS lookup API guide.
SSL Certificate
Is infrastructure ready to deceive? A certificate provisioned hours or days after domain registration — especially from a free CA like Let's Encrypt — indicates the site is being built out for active use. The SSL certificate monitoring API guide covers change detection patterns.
Step 1: Generate Your Watchlist of Variants
The variant list is the foundation of the pipeline. It must be comprehensive enough to catch real attack patterns without generating so many candidates that API costs become prohibitive. The function below implements the four mutation categories.
HOMOGLYPHS = {
'a': ['а', '@'], # Cyrillic а, at-sign
'e': ['е', '3'], # Cyrillic е, digit 3
'i': ['і', '1', 'l'], # Cyrillic і, digit 1, lowercase L
'o': ['о', '0'], # Cyrillic о, digit 0
's': ['$', '5'],
'l': ['1', 'I'],
}
BRAND_KEYWORDS = [
'login', 'secure', 'support', 'account',
'verify', 'update', 'help', 'service', 'portal',
]
WATCHLIST_TLDS = ['.net', '.io', '.co', '.org', '.eu', '.app', '.dev']
def generate_variants(brand: str, primary_tld: str = '.com') -> list[str]:
variants = set()
b = brand.lower()
# --- TLD variants ---
for tld in WATCHLIST_TLDS:
variants.add(f"{b}{tld}")
# --- Keyword combinations ---
for kw in BRAND_KEYWORDS:
variants.add(f"{b}-{kw}{primary_tld}")
variants.add(f"{kw}-{b}{primary_tld}")
# --- Typosquatting: character transpositions ---
for i in range(len(b) - 1):
swapped = list(b)
swapped[i], swapped[i+1] = swapped[i+1], swapped[i]
variants.add(f"{''.join(swapped)}{primary_tld}")
# --- Typosquatting: character omissions ---
for i in range(len(b)):
variants.add(f"{b[:i] + b[i+1:]}{primary_tld}")
# --- Typosquatting: character duplications ---
for i in range(len(b)):
variants.add(f"{b[:i] + b[i] + b[i:]}{primary_tld}")
# --- Homoglyph substitutions ---
for i, char in enumerate(b):
for replacement in HOMOGLYPHS.get(char, []):
mutated = b[:i] + replacement + b[i+1:]
variants.add(f"{mutated}{primary_tld}")
# Remove the original domain itself
variants.discard(f"{b}{primary_tld}")
return sorted(variants)
# Example: generate_variants("acmecorp") → 80+ candidatesStep 2: Enrich Each Variant with the API
The enrichment pipeline runs WHOIS, DNS, and SSL checks concurrently per domain using asyncio . Availability is checked first — unregistered domains are skipped to avoid burning API credits on empty records. For the concurrency pattern and retry logic across large domain lists, see Bulk WHOIS: How to Query Multiple Domains at Once.
# pip install aiohttp
import asyncio
import aiohttp
API_KEY = "YOUR_API_KEY"
BASE = "https://whoisjson.com/api/v1"
HEADERS = {"Authorization": f"TOKEN={API_KEY}"}
async def get(session: aiohttp.ClientSession, path: str, domain: str) -> dict:
try:
async with session.get(
f"{BASE}/{path}",
params={"domain": domain},
headers=HEADERS,
timeout=aiohttp.ClientTimeout(total=12),
) as resp:
return await resp.json() if resp.status == 200 else {}
except Exception:
return {}
async def enrich_domain(session: aiohttp.ClientSession, domain: str) -> dict | None:
"""Return None for unregistered domains to skip downstream API calls."""
avail = await get(session, "domain-availability", domain)
if avail.get("available", True):
return None # not registered — skip
whois, dns, ssl = await asyncio.gather(
get(session, "whois", domain),
get(session, "nslookup", domain),
get(session, "ssl-cert-check", domain),
)
return {"domain": domain, "whois": whois, "dns": dns, "ssl": ssl}
Step 3: Score and Triage
Each enriched domain is passed through a weighted scoring function. The model is deliberately simple — four signals, each independently verifiable, with weights calibrated to real-world attack patterns.
| Signal | Field(s) | Points | Rationale |
|---|---|---|---|
| Domain registered < 30 days ago | age.isNewlyRegistered | +40 | Strongest single indicator — phishing infrastructure is almost always newly registered |
| Active MX record | dns.MX | +20 | Domain can send email — BEC / phishing amplifier |
| SSL cert issued within 7 days of registration | ssl.valid_from vs whois.created | +20 | Site is being actively built out; infrastructure is production-ready |
| Registrant fully redacted (GDPR shield) | contacts.owner | +20 | Adversarial registrations overwhelmingly use privacy proxies |
from datetime import datetime, timezone
def score_domain(e: dict) -> dict:
pts = 0
flags = []
w = e.get("whois") or {}
d = e.get("dns") or {}
s = e.get("ssl") or {}
# +40 — newly registered (< 30 days)
if (w.get("age") or {}).get("isNewlyRegistered"):
pts += 40; flags.append("newly_registered")
# +20 — active MX record
if d.get("MX"):
pts += 20; flags.append("mx_active")
# +20 — SSL cert issued within 7 days of domain creation
ssl_from = (s.get("valid_from") or "")[:10]
whois_crt = (w.get("created") or "")[:10]
if ssl_from and whois_crt:
try:
delta = (
datetime.fromisoformat(ssl_from) -
datetime.fromisoformat(whois_crt)
).days
if 0 <= delta <= 7:
pts += 20; flags.append("cert_issued_quickly")
except ValueError:
pass
# +20 — fully redacted registrant
owner = ((w.get("contacts") or {}).get("owner") or [{}])[0]
if not any([owner.get("name"), owner.get("organization"), owner.get("email")]):
pts += 20; flags.append("redacted_contacts")
pts = min(pts, 100)
tier = "HIGH" if pts >= 60 else ("MEDIUM" if pts >= 30 else "LOW")
return {
"domain": e["domain"],
"score": pts,
"risk": tier,
"flags": flags,
"created": w.get("created"),
"registrar": (w.get("registrar") or {}).get("name"),
"a_records": d.get("A", []),
"mx_records": [mx.get("exchange") for mx in d.get("MX", [])],
"ssl_issuer": (s.get("issuer") or {}).get("O"),
"ssl_expiry": s.get("valid_to"),
}Example output for a high-risk brand lookalike:
{
"domain": "acmecorp-login.com",
"score": 100,
"risk": "HIGH",
"flags": ["newly_registered", "mx_active", "cert_issued_quickly", "redacted_contacts"],
"created": "2026-04-26 07:12:00",
"registrar": "Namecheap, Inc.",
"a_records": ["104.21.45.89"],
"mx_records": ["mail.acmecorp-login.com"],
"ssl_issuer": "Let's Encrypt",
"ssl_expiry": "2026-07-25T07:12:00.000Z"
}Step 4: Set Up Continuous Monitoring
Running a one-shot enrichment sweep is useful for an initial brand audit. Continuous brand protection requires something different: detection at registration time, not 24 hours later. There are two complementary approaches.
Domain monitoring endpoints
The WhoisJSON domain monitoring feature watches a domain continuously and fires a notification (webhook or email) the moment a WHOIS, DNS, or SSL change is detected. Add your highest-priority variants to a monitoring slot — newly registered domains and those with an active A record — and you get change alerts without polling. For a full walkthrough of alert configuration and threshold tuning, see domain monitoring for security teams.
Tiered polling for large watchlists
For watchlists that exceed your monitoring slot quota, implement tiered polling: HIGH-risk domains re-enriched daily, MEDIUM-risk weekly, LOW-risk monthly. This reduces total API credits by an order of magnitude versus polling every variant at the same frequency. The availability check (1 credit) acts as a gate — only re-run the full 3-call enrichment when the domain transitions from available to registered.
What to Do When You Find a Threat
Detection is only half the problem. When the pipeline returns a HIGH-risk domain, three response tracks run in parallel.
- Defensive registration: if the domain is not yet registered (available: true on the availability endpoint), register it immediately through your registrar. This is the fastest and cheapest mitigation — it pre-empts the attack before it starts.
- Takedown request: for domains already registered and actively hosting malicious content, file an abuse report with the registrar (the
registrar.emailfield from the WHOIS response is the abuse contact). For sustained campaigns, the ICANN UDRP process provides a formal dispute resolution mechanism for domain names that infringe trademarks. - Internal DNS blocking / SIEM alert: push the domain and its A records to your internal DNS blocklist and SIEM immediately, regardless of takedown status. Takedowns take days; DNS blocking is instant and protects users while the formal process runs.
Full Working Example
The script below combines all four steps into a single runnable pipeline: variant generation, availability gating, parallel enrichment, risk scoring, and alert output. Copy, drop in your API key and brand name, and run.
# pip install aiohttp
import asyncio, json
import aiohttp
from datetime import datetime
API_KEY = "YOUR_API_KEY"
BASE = "https://whoisjson.com/api/v1"
HEADERS = {"Authorization": f"TOKEN={API_KEY}"}
CONCURRENCY = 10 # tune to plan RPM / 4
HOMOGLYPHS = {'a': ['а','@'], 'e': ['е','3'], 'i': ['і','1'], 'o': ['о','0'], 's': ['$']}
KEYWORDS = ['login', 'secure', 'support', 'account', 'verify']
TLDS = ['.net', '.io', '.co', '.org']
def generate_variants(brand: str, primary_tld: str = '.com') -> list[str]:
b, variants = brand.lower(), set()
for tld in TLDS:
variants.add(f"{b}{tld}")
for kw in KEYWORDS:
variants.add(f"{b}-{kw}{primary_tld}")
variants.add(f"{kw}-{b}{primary_tld}")
for i in range(len(b) - 1):
s = list(b); s[i], s[i+1] = s[i+1], s[i]
variants.add(f"{''.join(s)}{primary_tld}")
for i in range(len(b)):
variants.add(f"{b[:i]+b[i+1:]}{primary_tld}")
variants.add(f"{b[:i]+b[i]+b[i:]}{primary_tld}")
for i, c in enumerate(b):
for r in HOMOGLYPHS.get(c, []):
variants.add(f"{b[:i]+r+b[i+1:]}{primary_tld}")
variants.discard(f"{b}{primary_tld}")
return sorted(variants)
async def get(s: aiohttp.ClientSession, path: str, domain: str) -> dict:
try:
async with s.get(f"{BASE}/{path}", params={"domain": domain},
headers=HEADERS,
timeout=aiohttp.ClientTimeout(total=12)) as r:
return await r.json() if r.status == 200 else {}
except Exception:
return {}
async def enrich(s: aiohttp.ClientSession, domain: str) -> dict | None:
avail = await get(s, "domain-availability", domain)
if avail.get("available", True):
return None
whois, dns, ssl = await asyncio.gather(
get(s, "whois", domain),
get(s, "nslookup", domain),
get(s, "ssl-cert-check", domain),
)
return {"domain": domain, "whois": whois, "dns": dns, "ssl": ssl}
def score(e: dict) -> dict:
pts, flags = 0, []
w, d, s = e.get("whois") or {}, e.get("dns") or {}, e.get("ssl") or {}
if (w.get("age") or {}).get("isNewlyRegistered"):
pts += 40; flags.append("newly_registered")
if d.get("MX"):
pts += 20; flags.append("mx_active")
ssl_from, crt = (s.get("valid_from") or "")[:10], (w.get("created") or "")[:10]
try:
if ssl_from and crt and 0 <= (datetime.fromisoformat(ssl_from) - datetime.fromisoformat(crt)).days <= 7:
pts += 20; flags.append("cert_issued_quickly")
except ValueError:
pass
owner = ((w.get("contacts") or {}).get("owner") or [{}])[0]
if not any([owner.get("name"), owner.get("organization"), owner.get("email")]):
pts += 20; flags.append("redacted_contacts")
pts = min(pts, 100)
return {"domain": e["domain"], "score": pts,
"risk": "HIGH" if pts >= 60 else ("MEDIUM" if pts >= 30 else "LOW"),
"flags": flags, "created": w.get("created"),
"registrar": (w.get("registrar") or {}).get("name")}
def alert_if_high_risk(result: dict) -> None:
if result["risk"] == "HIGH":
print(f"[ALERT] HIGH-RISK domain detected: {result['domain']}")
print(f" Score: {result['score']}/100 — Flags: {', '.join(result['flags'])}")
print(f" Created: {result['created']} — Registrar: {result['registrar']}")
async def run(brand: str) -> list[dict]:
variants = generate_variants(brand)
sem = asyncio.Semaphore(CONCURRENCY)
results = []
async def bounded(domain: str):
async with sem:
return await enrich(session, domain)
async with aiohttp.ClientSession() as session:
enriched = await asyncio.gather(*[bounded(d) for d in variants])
for e in enriched:
if e is not None:
r = score(e)
alert_if_high_risk(r)
results.append(r)
return sorted(results, key=lambda x: x["score"], reverse=True)
if __name__ == "__main__":
brand = "acmecorp" # replace with your brand name
output = asyncio.run(run(brand))
print(json.dumps(output, indent=2, ensure_ascii=False))
Conclusion
Brand protection domain monitoring is not a dashboard you check once a week — it is an automated pipeline that runs continuously, detects threats at registration time, and hands actionable intelligence to the people who can act on it. The four signals covered here — availability, WHOIS age and registrant opacity, DNS configuration, and SSL issuance timing — are independently weak but jointly conclusive. A domain that scores HIGH across all four should be escalated immediately; the evidence is rarely ambiguous.
The pipeline in this guide processes a typical brand watchlist (80–120 variants) in under 60 seconds with the free tier, and scales to continuous daily sweeps of thousands of variants on a Pro plan. The monitoring slot layer on top means you are notified of changes within minutes — not after the attack is already underway.
Start Monitoring Your Brand
1,000 free API requests/month — WHOIS, DNS, SSL, availability, and monitoring. No credit card.
Get Your Free API KeyScale to Daily Automation
Pro plan: 30,000 requests/month, 40 RPM. Enough for daily sweeps of 200+ brand variants.
View Pricing