Security

Email Domain Reputation API: Detect Signup Fraud with WHOIS and DNS

A practical guide for SaaS teams that need to score the domain behind a signup email: domain age, MX records, SPF, DMARC, registrar data, EPP status, and safe decision logic.

May 19, 202612 min readSecurity · Fraud Prevention · WHOIS · DNS

Introduction

A signup form usually treats an email address as a delivery channel: send a verification link, wait for the click, then create the account. That proves the user controls an inbox. It does not prove the domain behind the inbox represents a legitimate company, a stable customer, or a low-risk identity.

Trial abuse, fake B2B accounts, coupon abuse, affiliate fraud, and low-quality lead capture often hide behind domains that look plausible at first glance. The pattern is familiar: a custom domain registered yesterday, privacy-shielded WHOIS data, no meaningful mail infrastructure, and just enough DNS to receive a verification email. A human reviewer can spot this after investigation. A product team needs the signal at signup time.

This is where an email domain reputation workflow helps. Extract the domain after the@ sign, query public domain intelligence, and turn the result into a risk score. WhoisJSON provides the building blocks through documented endpoints: the WHOIS API for registration age, registrar and EPP status, and the DNS Lookup API for MX, TXT, DMARC, BIMI, MTA-STS and TLS-RPT records. This article goes deeper than the short fraud-prevention section in WHOIS API use cases.

What Is Email Domain Reputation?

Email domain reputation is a risk assessment of the domain part of an email address, not the mailbox itself. For [email protected], the domain to inspect is example.com. The goal is not to decide whether the user is good or bad from one field. The goal is to separate normal business domains from domains that deserve more friction, review, or lower trust.

A real company domain usually has history. It was registered months or years ago, has stable nameservers, receives email through MX records, publishes at least some TXT-based mail policy, and does not carry suspicious EPP states. A disposable abuse domain often has the opposite profile: very recent creation, minimal DNS, hidden or missing ownership data, and no operational footprint beyond account creation.

Treat reputation as a routing signal. A weak domain should not always mean "block". Better outcomes usually come from progressive decisions: allow, require email verification, require payment card, ask for company details, send to manual review, or block only when several high-risk signals agree.

Signals That Matter at Signup

The useful signals are simple, public, and fast enough to compute before or immediately after account creation.

SignalEndpointWhy it matters
Domain registration age/whoisA domain created in the last few days is more suspicious than a domain with years of history.
age.isNewlyRegistered andage.isYoung/whoisPre-computed RDAP helper fields for recent-domain risk scoring.
Created and expiry dates/whoisShort-lived or newly created domains deserve different treatment than established corporate domains.
EPP status/whoisHold, pending delete, or transfer-related states can reveal operational or abuse issues.
Registrar/whoisUseful as context in your own internal model, especially when combined with past abuse history.
MX records/nslookupA business email domain should usually be configured to receive mail.
SPF and other TXT records/nslookupTXT records can reveal whether the domain is configured for legitimate outbound email.
DMARC/nslookupMissing or weak DMARC is not fraud by itself, but it lowers confidence for business identity checks.
MTA-STS and TLS-RPT/nslookupStronger mail-security posture is a positive trust signal for mature organizations.

The Swagger definition for/nslookup documents A, AAAA, CAA, CNAME, MX, NS, SOA, TXT, DMARC, BIMI, MTASTS, and TLSRPT in one response. That makes it practical to inspect mail posture without chaining many DNS calls yourself.

The API Contract You Need

WhoisJSON API requests use theAuthorization header with aTOKEN= prefix. The production base URL is:

Base URLText
https://whoisjson.com/api/v1

For signup scoring, two endpoints usually cover the first version of the workflow.

WHOIS requestcURL
curl "https://whoisjson.com/api/v1/whois?domain=example.com" \
  -H "Authorization: TOKEN=YOUR_API_KEY"
DNS requestcURL
curl "https://whoisjson.com/api/v1/nslookup?domain=example.com" \
  -H "Authorization: TOKEN=YOUR_API_KEY"

Every API response includes aRemaining-Requests header according to the OpenAPI documentation. Track it in production, especially if signup scoring runs on the critical path. For retry behavior, see the guide on WHOIS API rate limits and 429 errors.

A Practical Signup Risk Score

Start with a transparent model that your support, sales, and security teams can understand. You can tune the weights later from your own abuse outcomes.

ConditionPointsReason
age.isNewlyRegistered is true+35Domain was created very recently.
age.isYoung is true+15Domain is relatively new but not necessarily high risk.
No MX records+25Weak signal for a claimed business email domain.
No DMARC record+10Lowers confidence in mature mail setup.
No SPF-like TXT record+10Suggests minimal outbound mail posture.
EPP hold or pending delete signal+20Domain may be suspended, disputed, or operationally unstable.
Fully redacted or absent owner contact data+10Common and often legitimate, but useful as a secondary signal.
0-24Allow normally
25-49Verify email and monitor
50-74Step-up verification
75+Manual review or block

The exact thresholds depend on your business. A developer tool with a generous free tier may tolerate less risk than an enterprise demo form where every lead is reviewed by sales anyway.

Python Example: Score an Email Domain

This example extracts the domain from an email address, calls WHOIS and DNS lookup, and returns a score with reasons. It only uses documented response fields from the WhoisJSON OpenAPI file.

email_domain_score.pyPython
import requests

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


def email_domain(email: str) -> str:
    if "@" not in email:
        raise ValueError("Invalid email address")
    return email.rsplit("@", 1)[1].strip().lower()


def parse_remaining_requests(value: str | None) -> int | None:
    if value is None:
        return None
    try:
        return int(value)
    except ValueError:
        return None


def get_json(path: str, domain: str) -> tuple[dict, int | None]:
    response = requests.get(
        f"{BASE_URL}{path}",
        headers=HEADERS,
        params={"domain": domain},
        timeout=10,
    )
    response.raise_for_status()
    remaining = parse_remaining_requests(response.headers.get("Remaining-Requests"))
    return response.json(), remaining


def has_spf(txt_records: list) -> bool:
    return any(str(record).lower().startswith("v=spf1") for record in txt_records or [])


def score_email_domain(email: str) -> dict:
    domain = email_domain(email)
    whois, whois_remaining = get_json("/whois", domain)
    dns, dns_remaining = get_json("/nslookup", domain)

    score = 0
    reasons = []

    age = whois.get("age") or {}
    if age.get("isNewlyRegistered"):
        score += 35
        reasons.append("domain registered within 30 days")
    elif age.get("isYoung"):
        score += 15
        reasons.append("domain registered within 365 days")

    status_values = " ".join(whois.get("status") or []).lower()
    if any(flag in status_values for flag in ["clienthold", "serverhold", "pendingdelete"]):
        score += 20
        reasons.append("domain has hold or pending delete status")

    contacts = (whois.get("contacts") or {}).get("owner") or []
    if not contacts:
        score += 10
        reasons.append("owner contact is absent or redacted")

    mx_records = dns.get("MX") or []
    if not mx_records:
        score += 25
        reasons.append("no MX records")

    txt_records = dns.get("TXT") or []
    if not has_spf(txt_records):
        score += 10
        reasons.append("no SPF-like TXT record")

    dmarc_records = dns.get("DMARC") or []
    if not dmarc_records:
        score += 10
        reasons.append("no DMARC record")

    if score >= 75:
        decision = "manual_review_or_block"
    elif score >= 50:
        decision = "step_up_verification"
    elif score >= 25:
        decision = "allow_with_monitoring"
    else:
        decision = "allow"

    remaining_values = [
        value for value in [whois_remaining, dns_remaining]
        if value is not None
    ]

    return {
        "email": email,
        "domain": domain,
        "score": min(score, 100),
        "decision": decision,
        "reasons": reasons,
        "remaining_requests": min(remaining_values) if remaining_values else None,
    }


print(score_email_domain("[email protected]"))

Node.js Example: Signup Middleware

In a real SaaS app, you usually do not want to block the request thread forever. Run the check with explicit timeouts, store the score, and route the account into the right onboarding path.

signup-risk.jsJavaScript
const API_KEY = process.env.WHOISJSON_API_KEY;
const BASE_URL = 'https://whoisjson.com/api/v1';

function getEmailDomain(email) {
  const parts = String(email).toLowerCase().trim().split('@');
  if (parts.length !== 2 || !parts[1]) {
    throw new Error('Invalid email address');
  }
  return parts[1];
}

function parseRemainingRequests(value) {
  if (value === null) return null;
  const parsed = Number.parseInt(value, 10);
  return Number.isNaN(parsed) ? null : parsed;
}

async function fetchJson(path, domain) {
  const url = new URL(`${BASE_URL}${path}`);
  url.searchParams.set('domain', domain);

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 10000);

  try {
    const response = await fetch(url, {
      headers: { Authorization: `TOKEN=${API_KEY}` },
      signal: controller.signal
    });

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

    return {
      data: await response.json(),
      remainingRequests: parseRemainingRequests(response.headers.get('Remaining-Requests'))
    };
  } finally {
    clearTimeout(timeout);
  }
}

function hasSpf(txtRecords = []) {
  return txtRecords.some((record) =>
    String(record).toLowerCase().startsWith('v=spf1')
  );
}

async function scoreSignupEmail(email) {
  const domain = getEmailDomain(email);
  const [whoisResult, dnsResult] = await Promise.all([
    fetchJson('/whois', domain),
    fetchJson('/nslookup', domain)
  ]);

  if (whoisResult.error || dnsResult.error) {
    return {
      email,
      domain,
      score: 30,
      decision: 'allow_with_monitoring',
      reasons: ['domain intelligence lookup incomplete'],
      remaining_requests: null
    };
  }

  const whois = whoisResult.data;
  const dns = dnsResult.data;
  let score = 0;
  const reasons = [];
  const age = whois.age || {};

  if (age.isNewlyRegistered) {
    score += 35;
    reasons.push('domain registered within 30 days');
  } else if (age.isYoung) {
    score += 15;
    reasons.push('domain registered within 365 days');
  }

  const status = (whois.status || []).join(' ').toLowerCase();
  if (status.includes('clienthold') || status.includes('serverhold') || status.includes('pendingdelete')) {
    score += 20;
    reasons.push('domain has hold or pending delete status');
  }

  const ownerContacts = (whois.contacts || {}).owner || [];
  if (!ownerContacts.length) {
    score += 10;
    reasons.push('owner contact is absent or redacted');
  }

  if (!(dns.MX || []).length) {
    score += 25;
    reasons.push('no MX records');
  }

  if (!hasSpf(dns.TXT || [])) {
    score += 10;
    reasons.push('no SPF-like TXT record');
  }

  if (!(dns.DMARC || []).length) {
    score += 10;
    reasons.push('no DMARC record');
  }

  const decision =
    score >= 75 ? 'manual_review_or_block' :
    score >= 50 ? 'step_up_verification' :
    score >= 25 ? 'allow_with_monitoring' :
    'allow';

  const remainingValues = [
    whoisResult.remainingRequests,
    dnsResult.remainingRequests
  ].filter((value) => value !== null);

  return {
    email,
    domain,
    score: Math.min(score, 100),
    decision,
    reasons,
    remaining_requests: remainingValues.length
      ? Math.min(...remainingValues)
      : null
  };
}

module.exports = { scoreSignupEmail };

Avoiding False Positives

The most expensive mistake is treating a single technical signal as proof of fraud. New startups register new domains. Small businesses misconfigure DMARC. Some legitimate companies hide WHOIS contacts for privacy. A good onboarding model uses domain intelligence to decide the next step, not to replace judgment entirely.

  • Do not block only because a domain is new. Combine age with MX, DMARC, user behavior, payment risk, IP reputation, and account velocity.
  • Separate consumer email from business email. Gmail, Outlook, Yahoo and similar domains need a different policy than custom company domains.
  • Keep an unknown state. If an API call times out or returns 429, mark the domain as unknown and retry later. Do not convert lookup failure into "safe" or "fraud".
  • Review outcomes. Feed confirmed abuse and false positives back into your scoring weights.

If your workflow involves high-volume batches, use the patterns from the Bulk WHOIS lookup guide and avoid uncontrolled concurrency.

Where This Fits in Your Stack

Email domain reputation is not a replacement for email verification, CAPTCHA, device fingerprinting, payment risk, or abuse monitoring. It is a low-friction signal that arrives early enough to change the onboarding path.

Signup eventDomain intelligence actionProduct decision
Account created with business emailScore WHOIS age and DNS mail recordsAllow or add step-up verification
Free trial startedStore score on account profileAdjust quota, review, or sales routing
High usage in first hourRe-check domain and account cohortTrigger abuse review
Payment addedCompare risk score with billing identityReduce friction for trusted accounts

For security-heavy workflows, connect this article with phishing domain detection and newly registered domain detection. For customer-domain onboarding, the DNS Lookup API guide explains broader DNS record handling.

FAQ

What is an email domain reputation API?

An email domain reputation API checks the domain part of an email address and returns risk signals such as registration age, EPP status, MX records, SPF, DMARC, and mail-security DNS records.

Can domain age detect signup fraud by itself?

No. Domain age is useful because many abuse domains are young, but legitimate companies also launch new domains. Combine it with DNS posture and account behavior.

Which endpoints are required?

Start with /whois and /nslookup. Add /ssl-cert-check when website activation, certificate issuer, SAN coverage, or certificate timing matters to your scoring model.

Should I run this before or after account creation?

For low-latency products, create the account first, run the score immediately, then decide the onboarding path. For high-risk products, run the check before granting quota or access to expensive workflows.

Conclusion

Signup fraud often looks like a normal email verification problem until you inspect the domain behind the address. WHOIS and DNS data give you a practical way to tell the difference between an established business domain and a newly registered, minimally configured identity.

The best first version is intentionally simple: extract the email domain, query WHOIS and DNS, compute a transparent score, and route users to allow, monitoring, step-up verification, manual review, or block. The score will improve as your own abuse data teaches you which signals matter most.

Build signup risk scoring with WhoisJSON

Use WHOIS, DNS, SSL, availability, subdomains and monitoring under one API key. Start with 1,000 free requests/month.

Fraud Prevention

Score Email Domains Before They Burn Your Free Tier

Query WHOIS and DNS signals with one API key: domain age, EPP status, MX, TXT, DMARC, MTA-STS and TLS-RPT. Start with 1,000 free requests/month.

WHOIS + RDAPMX and TXT recordsDMARC and mail security1,000 free requests/month