AnswerLineStart free

, Brand Monitoring · Google · Advertising · Engineering

Typosquatting detection: find lookalike domains in Google results, ads and certificate logs

To detect typosquatting that reaches customers, combine three sources: a list of permutation candidates of your domain (what could be registered), certificate transparency logs (what someone got a certificate for), and Google results and ads for your branded queries (what is being shown to people searching for you). The third source ranks the other two, because a lookalike that ranks for your brand name or runs ads on it is exposed right now. The workflow below is for brand owners defending their own names.

Typosquatting is registering a domain that differs slightly from a legitimate one to capture mistyped traffic or impersonate the brand. Combosquatting adds a word instead of changing letters (acme-login.com, acmeoutlet.shop). Both are called lookalike domains below.

Permutation candidates

Candidates are generated from your registered domain by a small set of transformations. For acme.com:

Technique Example
Omission acm.com, ame.com
Repetition accme.com, acmme.com
Transposition amce.com, came.com
Adjacent-key replacement acne.com, scme.com
Homoglyph acrne.com (rn for m), internationalised domains with lookalike Unicode characters
Hyphenation ac-me.com
Vowel swap acma.com, ecme.com
TLD swap acme.co, acme.shop, acme.net
Combosquatting acme-login.com, acmesupport.com, myacme.com

dnstwist

You do not need to write a generator from scratch. dnstwist is an open-source “domain name permutation engine for detecting homograph phishing attacks, typo squatting, and brand impersonation” (README, checked 2026-09-17). It generates variants and checks which are registered, can filter to registered domains only, detects rogue MX hosts, compares page similarity with fuzzy hashes and screenshot similarity with perceptual hashes, and exports CSV and JSON. It is Apache-2.0 licensed and available through pip, several Linux package managers, Homebrew and Docker, with a browser version at dnstwist.it.

A typical defensive workflow exports registered candidates daily to a file your pipeline reads.

A minimal generator for matching

For matching search results you also want a fast in-process check. The generator below produces the common single-edit variants of the label and a combosquatting test; use dnstwist’s output as the richer candidate list alongside it.

import string

KEYBOARD = {"a": "qwsz", "c": "xdfv", "e": "wsdr", "m": "njk", "o": "iklp", "i": "ujko", "n": "bhjm",
            "s": "awedxz", "r": "edft", "t": "rfgy", "l": "kop", "u": "yhji"}
HOMOGLYPH = {"m": ["rn"], "w": ["vv"], "l": ["1", "i"], "o": ["0"], "i": ["1", "l"], "e": ["3"]}
COMBO_WORDS = ("login", "signin", "secure", "verify", "support", "help", "account", "outlet", "store",
               "shop", "sale", "official", "app", "pay", "billing")

def variants(label: str) -> set[str]:
    out = set()
    for i in range(len(label)):
        out.add(label[:i] + label[i + 1:])                                   # omission
        out.add(label[:i] + label[i] + label[i:])                            # repetition
        if i < len(label) - 1:
            out.add(label[:i] + label[i + 1] + label[i] + label[i + 2:])     # transposition
            out.add(label[:i + 1] + "-" + label[i + 1:])                     # hyphenation
        for k in KEYBOARD.get(label[i], ""):
            out.add(label[:i] + k + label[i + 1:])                           # adjacent key
        for g in HOMOGLYPH.get(label[i], []):
            out.add(label[:i] + g + label[i + 1:])                           # ASCII homoglyph
    out.discard(label)
    return {v for v in out if v and all(c in string.ascii_lowercase + string.digits + "-" for c in v)}

def classify(host: str, brand: str, owned: set[str], typo_labels: set[str]) -> str | None:
    host = host.lower().removeprefix("www.")
    if not host or any(host == d or host.endswith("." + d) for d in owned):
        return None
    labels = host.split(".")
    registrable = labels[-2] if len(labels) >= 2 else labels[0]
    if host.startswith("xn--") or any(l.startswith("xn--") for l in labels):
        return "idn"                                                          # punycode: inspect manually
    if registrable in typo_labels:
        return "typo"
    squashed = registrable.replace("-", "")
    if brand in squashed and squashed != brand and any(w in squashed for w in COMBO_WORDS):
        return "combo"
    if brand in squashed or any(brand in l for l in labels[:-2]):
        return "brand_in_host"                                                # e.g. acme.example-shop.com
    return None

registrable = labels[-2] is a simplification that is wrong for suffixes such as co.uk. In production, split hosts with a Public Suffix List library.

Certificate transparency

Certificate transparency logs are “append-only” and “publicly auditable”, and a CT monitor subscribed to your domain sends updates when certificates for it are logged (how CT works, checked 2026-09-17). Because Chrome and Safari help enforce CT, a lookalike site using a publicly trusted HTTPS certificate usually leaves a record.

crt.sh, operated by Sectigo, searches those logs by identity (domain or organisation name), certificate fingerprint or crt.sh ID (crt.sh). Useful defensive checks:

A certificate is evidence that a name was prepared for HTTPS, not that it is malicious. Many lookalikes are defensive registrations, parked domains or unrelated businesses.

What search results and ads show

Permutation lists and certificates tell you what exists. Google tells you what is being shown to people who search for you. Run branded queries through this API and read three fields:

Field Meaning for typosquatting
result.organicResults[].link A lookalike ranking organically for your brand, product or support queries
result.ads[].domain The advertiser domain of a text ad (type RESULT) on your branded query
result.ads[].displayedUrl The URL shown in the ad; compare its host with domain and with your owned domains

Also check result.aioverview.sources[].url when you request include.aioverview, since a lookalike cited in an AI Overview is exposed in a different way.

Queries that attract impersonators

Impersonators target moments when a user is ready to type credentials or pay:

  1. acme login, acme sign in, acme account
  2. acme customer service, acme support phone number, acme refund
  3. acme app download
  4. acme outlet, acme sale, acme discount code
  5. Each product name plus official

Run each on desktop and a mobile device (ios or android) in each market you sell in, because ads and result layouts differ.

Submit the checks

import datetime as dt, os, requests

API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
HOOK = {"url": "https://hooks.example.com/lookalikes"}
QUERIES = ["acme login", "acme customer service", "acme app download", "acme outlet", "acme discount code"]

def submit(countries=("US", "GB")):
    now = dt.datetime.now(dt.timezone.utc)
    slot = f"{now:%Y-%m-%d}T{now.hour - now.hour % 6:02d}"
    tasks = [{"taskType": "GOOGLE",
              "payload": {"query": q, "country": c, "device": device},
              "idempotencyKey": f"typo|{q}|{c}|{device}|{slot}",
              "priority": 8, "webhook": HOOK}
             for q in QUERIES for c in countries for device in ("desktop", "ios")]
    res = requests.post(f"{API}/v1/async/task/batch", json=tasks, headers=HEADERS, timeout=60)
    res.raise_for_status()
    return res.json()["summary"]

Extract and match

from urllib.parse import urlsplit

BRAND = "acme"
OWNED = {"acme.com", "acme.co.uk", "acme-status.com"}
TYPO_LABELS = variants(BRAND) | set(load_dnstwist_labels())   # your daily dnstwist export

def hostname(value):
    if not value:
        return ""
    if "://" not in value:
        value = "https://" + value
    return (urlsplit(value).hostname or "").lower()

def sightings(result):
    for o in result.get("organicResults", []):
        yield "organic", o.get("position"), hostname(o.get("link")), o.get("link"), o.get("title")
    for a in result.get("ads", []):
        if a.get("type") != "RESULT":
            continue
        d, shown = hostname(a.get("domain")), hostname(a.get("displayedUrl"))
        yield "ad", a.get("position"), d, a.get("url"), a.get("title")
        if shown and shown.removeprefix("www.") != d.removeprefix("www."):
            yield "ad_displayed", a.get("position"), shown, a.get("displayedUrl"), a.get("title")
    for s in (result.get("aioverview") or {}).get("sources", []):
        yield "aio_source", s.get("position"), hostname(s.get("url")), s.get("url"), s.get("label")

def candidates(result):
    for channel, position, host, url, title in sightings(result):
        kind = classify(host, BRAND, OWNED, TYPO_LABELS)
        if kind:
            yield {"channel": channel, "position": position, "host": host, "kind": kind, "url": url, "title": title}

In the webhook handler, verify Webhook-Signature on the raw body, ignore "test": true, deduplicate by task.id, and pass response.result to candidates (webhooks). Store every candidate with the task id, query, market, device and time. Store the full response as evidence; add include.html to the payload if you want the raw page HTML as well.

Ad url values are often Google click-redirect links, so identify advertisers by domain, not url. An ad whose displayedUrl host differs from its domain is not necessarily abusive, but it is worth a look when either host resembles your brand.

Triage

Most candidates are not attacks. Score them before anyone spends time on them.

Signal Points
Seen in an ad on a branded query +3
Seen in organic top 10 for a login, support or payment query +3
Exact typo or homoglyph candidate (typo, idn) +2
Combosquatting with a credential or payment word (login, verify, pay) +2
Certificate issued in the last 30 days +1
Seen in 2+ checks +1
Title or ad text uses your brand name and “official” +1
Known reseller, affiliate, review site or your own defensive registration −5

Then an analyst, in an isolated environment:

  1. Records registration data (WHOIS or RDAP), DNS records and certificate details.
  2. Captures the page, for example with a private urlscan.io scan, which its documentation says is “only visible to you” (visibility, checked 2026-09-17).
  3. Decides a category: phishing, counterfeit, impersonation, parked, unrelated, legitimate.
  4. Chooses the reporting route below.

Never enter credentials or payment details on a suspected site, and do not interact with it beyond capturing evidence.

Reporting routes

Choose by what the domain is doing. The routes below are those documented on the linked pages, checked 2026-09-17. Legal routes need counsel; this is not legal advice.

Phishing page

Google Safe Browsing has a “Report a Page to Google Safe Browsing” form (report). Also notify the hosting provider and registrar through their abuse contacts.

Counterfeit goods in ads

Google Ads prohibits “the sale or promotion for sale of counterfeit goods”, and its policy points to a Google Ads counterfeit complaint form; it states that a response to a valid complaint “may include removing the content or terminating the user’s account” (counterfeit goods policy).

Impersonation in ads

Google Ads’ misrepresentation policy prohibits ads that “make it seem like you’re supported by another brand, organization or government entity when you’re not” (misrepresentation policy).

Trademark use in ad text

Google Ads’ trademark policy restricts certain uses of trademarks in ad text, directs trademark owners to its “Report Content On Google” process, and does not restrict “using trademarks as keywords” (trademarks policy). A competitor bidding on your brand name is therefore not, by itself, a policy violation.

The domain itself

For trademark-based disputes over a domain name, ICANN’s Uniform Domain-Name Dispute-Resolution Policy requires the complainant to show that the domain “is identical or confusingly similar to a trademark or service mark in which the complainant has rights”, that the registrant has “no rights or legitimate interests” in it, and that it “has been registered and is being used in bad faith”; remedies are limited to cancellation or transfer (UDRP). Complaints go to an approved dispute-resolution service provider (ICANN UDRP overview).

Log every report with the date, route, reference number and evidence, then keep checking. A case is closed only when the domain stops appearing across several consecutive checks.

Metrics

Metric Definition
Exposed lookalikes Distinct lookalike hosts seen in organic results, ads or AI Overview sources in a period
Ad exposure rate Branded query checks with at least one lookalike ad ÷ branded query checks
Time to detection First sighting in results − certificate log date or registration date, when known
Time to takedown Last sighting − report date, for cases reported
Recurrence Closed cases seen again within 30 days ÷ closed cases

Cost

A Google Search async task is 3 credits for one page, plus 2 with include.aioverview. The example above is 5 queries × 2 countries × 2 devices = 20 tasks per run; four runs a day is 80 tasks, 240 credits a day, about 7,200 a month without AI Overview. dnstwist and crt.sh add no API cost. See pricing.

This API does not return WHOIS, DNS, certificate or page content data. It shows what Google results and ads displayed for a query, market and device.

Pitfalls

The brand protection use case covers the wider pipeline for unauthorised ads, sellers and AI shopping answers, and OSINT tools for defenders covers the surrounding toolset. Request fields are on the Google Search engine page.

Questions

What is typosquatting?

Registering a domain that differs slightly from a real brand's domain, such as a missing letter, swapped letters, a lookalike character, an added word or a different top-level domain, to catch mistyped traffic or to impersonate the brand for phishing, fraud or counterfeit sales.

Why look for lookalike domains in search results instead of only in DNS?

Registration lists show which lookalikes exist; search results and ads show which ones are reaching people. A lookalike ranking for your brand name or advertising on it is actively exposed to customers and should be triaged first.

Is a competitor bidding on my brand name a typosquatting case?

Not by itself. Google Ads' trademark policy, checked 2026-09-17, does not restrict using trademarks as keywords, and restricts certain uses in ad text instead. A lookalike domain, impersonation or counterfeit offer is a different matter with its own reporting routes.

How do I report a lookalike domain?

Depending on what it does: report phishing pages to Google Safe Browsing, report ads under Google Ads policies such as counterfeit goods or misrepresentation, contact the registrar and hosting provider's abuse teams, and for trademark-based domain disputes consider the UDRP through an approved provider with legal counsel.

Can the API tell me who registered a domain?

No. It returns what Google results and ads showed, including the link, displayed URL and advertiser domain. Registration data, DNS records and page content come from other sources such as WHOIS or RDAP, DNS lookups and your analysts.

Try it on your own prompts

500 free credits a month, no card. One POST returns the answer, sources and citations as JSON.

Keep reading