# Your listing links to a working website that is not yours any more

Not one third-party record for a multi-location business carried its current website. The stale ones failed three ways, and only one of them fails a link checker.

Author: J.A. Watte
Published: September 6, 2026
Source: https://jwatte.com/blog/blog-what-directories-believe-about-your-business/

---

I audited what public datasets and directories held about a multi-location service business. Not one of the records in a large open places dataset carried the business's current website.

That number was not the interesting part. The interesting part was how the wrong ones were wrong, because a link checker would have cleared two thirds of them.

Third parties are already publishing facts about you. Some of those facts are years old, some were never right, and the wrong ones mostly fail in ways that pass every check you would think to run. The work is auditing what they say, proving your own version before you overwrite theirs, and filing the correction where it survives the next rebuild.

## Act 1: what they already say about you

### Three ways a website URL is dead, and only one fails a link checker

The stale website values in those records failed in three distinct ways.

**No DNS at all.** The host does not resolve. Any checker catches this.

**A host that resolves and returns a platform 404**, because the free site builder that once served it stopped. Your checker sees a response, and a naive one that only asks "did this connect" passes it.

**A healthy 200 that renders a completely different business.** The domain was sold, or it was a property the business no longer operates. Every checker on earth passes this one. It is the most damaging of the three, because a customer clicking through lands somewhere real and plausible that is not you.

The fix is to stop asserting on the status code and start asserting on the effective host:

```bash
curl -sS -L --max-time 15 -o /dev/null -w '%{http_code} %{url_effective}\n' "$url"
```

Fail unless the final host equals your canonical host **and** the code is 200. `%{url_effective}` after `-L` is the whole check. A 200 at a host that is not yours is a failure, and it is invisible to any test that stops at the number.

### An unregistered domain in a public dataset is a same-day incident

Triage those dead URLs by resolver failure alone and you conflate two very different states. A domain can be registered with no DNS records, which is untidy. Or nobody owns it, which means a string sitting in a public dataset as your official website is available for anyone to buy.

DNS cannot tell you which. Ask the registry directly:

```bash
curl -s -o /dev/null -w '%{http_code}\n' https://rdap.org/domain/example.com
```

A 404 means no registration exists. A 200 whose `events` array carries an `expiration` in the past means it is expiring. Treat a 404 here as urgent rather than as cleanup, and note that this is a check you should run on your own former domains too, not only on what a directory believes.

### They write your description and pick your category, and "unflattering" is not "wrong"

Third-party records frequently carry a description and a category that nobody at the business wrote. When you audit these, the most useful thing you can do is split the findings in two before showing anyone:

**Wrong** means factually false: a category you do not operate in, a service you do not offer, an address that is not yours.

**True but unflattering** means accurate and awkward: a blunt description, an unfashionable category, a photo from before the refit.

Only the first list is worth an owner's attention, and owner attention is the scarcest resource in this entire process. Handing over a mixed list of forty items guarantees that the eight that matter get the same treatment as the thirty-two that do not.

### Normalise identifiers before you compare, or you will generate work that did not need doing

Half of a first-pass "these do not match" list is usually formatting. A trailing slash, `http` against `https`, a `www` prefix, a tracking parameter, upper case in a path. Normalise both sides to a canonical form before diffing, or your audit produces a worklist padded with items that were already correct, and the real ones get lost in it.

## Act 2: are you actually missing, or does the dataset just not know that name

This act exists because the most expensive mistake in this whole area is concluding you are absent and then creating a record.

### Resolve identity by geometry, not by name

Locations that looked absent from a public dataset were present under names the business had retired years earlier. One sat under a completely unrelated former trading name at the correct address.

A name search returns nothing, which reads as a clean absence, and the obvious next step is to create the missing record. Now you have two records for one place, and if the old one carried years of accumulated ratings you have just split them and created a merge problem far harder than the gap you were closing.

Query by geography instead:

```
node(around:150,<lat>,<lon>);
out body;
```

Read back every name inside that radius regardless of what it says. For each unfamiliar one, prove or disprove continuity from the state business registry rather than guessing. A trading-name amendment on file is evidence; a similar-sounding name is not.

The same rule applies on commercial platforms with no query language. Pan the map pane over the address and enumerate everything within roughly 100 metres before any create call. The assertion worth encoding is `count(records within 100m) == expected`, evaluated before you are allowed to create anything.

### Derive the missing set from your own structured data, never by eye

A hand count of how many locations lacked a directory page came out at four. It was five, and the missed one was the weakest and least documented location, which is exactly the one that most needed the work. Enumerating by memory undercounts precisely the items you are least familiar with.

Script the set difference instead. Pull the `sameAs` array from the JSON-LD on each of your own location pages, diff it against your full location list, and let the difference be the worklist. It costs nothing and it regenerates on every build, so it stays true after you fix some of them.

### Count the denominator before you treat your absence as your failure

A set of locations was missing from an open map database, which read as a data hygiene problem on our side. Then I counted how many records that dataset held for that whole category across the entire small city. It was a single-digit number.

The dataset had almost no coverage of the category in that market. The business was not missing from a complete picture; the picture was not there. That changes the work from "correct our records" to "decide whether populating this dataset is worth it at all", which is a different conversation and usually a shorter one.

```
# records in the dataset for your category and market
[out:json];
node["<category-key>"](<bbox>);
out count;
```

Compare against a ground-truth count you can assemble independently. Below roughly a tenth, your absence carries no signal.

### A null from a source that blocks you is not evidence of absence

Some directories return 403 to every server-side path, including proxies. I confirmed this against a target by probing direct plus two separate egress paths and getting a uniform 403 on a page that loads fine in a browser.

An empty scripted result from that source would have supported a confident, sweeping claim, and it would have been worthless. **Every absence check needs a positive control**: query the same source for an entity you know for certain is listed, and if that also returns nothing, your checker is blocked and the run tells you nothing at all.

```python
assert positive_control_hits > 0, "checker is blocked; absence results are meaningless"
```

Then scope the published sentence to exactly what you checked and date it. Not "we are not listed on X" but "a manual signed-in check on YYYY-MM-DD found no listing for these locations on X."

## Act 3: prove the coordinate before you touch it

### The odd and even street-side test

A batch geocode produced coordinates that looked authoritative. Six decimal places, no confidence field, no flag. They were road centreline interpolations, wrong by tens of metres, enough to put a pin across the street or in the wrong parcel.

Spot-checking one address on a map passes, because the centreline is only metres from the building. The failure only shows when you compare siblings:

Take five or six addresses on the same street, some odd-numbered and some even. For an east to west street, compare their latitudes. **A real rooftop source separates them into two bands**, odds on one side and evens on the other, differing in the third decimal place. An interpolating source flattens them all into one narrow band, which is the road.

One sort of the values makes it obvious, and it takes about two minutes.

### The result type is the gate, not the presence of a coordinate

An open geocoder returns a coordinate for almost any query. A result classed `type=house` is a building. A result classed `class=highway`, `type=residential` is the road the address sits on. Both come back as numbers with identical precision.

Assert on the classification, not on the coordinate:

```bash
jq 'select(.type != "house") | {display_name, class, type}' batch-out.json
```

Where the gate fails, the correct output is **no coordinate**, not a coordinate with a caveat in a comment. A caveat does not survive being read into a spreadsheet.

### Require three independent confirmations before moving a pin

It is easy to build a confident refutation from data that constrains nothing. A source that says "this address is in this town" does not disprove a pin two miles away inside the same town, but it feels like evidence when you are already suspicious.

Before moving a published coordinate, get three independent confirmations of the correct one. And when you cannot derive a rooftop independently at all, publish nothing rather than your best guess.

### Grep the build before you change a coordinate, because everything embeds its own copy

One location's published coordinate was wrong, and the same wrong value appeared in nine separate places on its own site. A map embed, a structured data block, link hrefs, each carrying its own copy of the literal.

```bash
grep -rn '<lat-prefix>' dist/ | wc -l
```

The count is always higher than you expect. Fix the source of truth, then assert the literal appears zero times anywhere else.

## Act 4: identity keys, and everything that inherits them

### The first identifier-shaped hit is often a neighbour

Recovering a map platform's place identifier by name and address search returns a payload containing several businesses. Take the first identifier-shaped match and you can walk away with the identifier of the business next door, which then propagates into review links, structured data, and printed codes on physical signage. You have now printed a sign that sends your customers to someone else's listing.

Decode it and check it rather than trusting position in a response. For that platform's format, the identifier after its four-character prefix is base64url, and decodes to 17 bytes: an 8-byte little-endian cell id, a separator byte, and an 8-byte little-endian feature id. Byte-reverse each half and the two hex values must equal the feature-id pair from the URL you started with.

### The three encodings are not interchangeable, and the wrong one redirects instead of erroring

The same business is exposed as a place id, a decimal customer id, and a hex feature-id pair. The review-writing endpoint accepts only the first. The map-link form used in structured data wants the second. Substituting the "same" identity in the wrong encoding does not error. It redirects somewhere plausible.

Assert on the final URL and status rather than on "it loaded":

```bash
curl -sI -o /dev/null -w '%{http_code} %{redirect_url}\n' '<review-url>?placeid=<id>'
```

A 200, or a redirect to the platform's own sign-in, is a pass. A 302 to a generic map or search page is the failure, and it looks fine to a human clicking it.

## Act 5: what it actually costs to fix

### A submission confirmation is not an acceptance

Corrections submitted as a non-owner to established listings were mostly not applied. Some sat pending indefinitely. Creating a missing location as a non-owner was refused outright. Meanwhile image uploads to the same listings published immediately.

The work looked complete at submission time, and a confirmation screen is designed to make you feel that way.

**Record every submission with a timestamp and re-read the public record itself 72 hours later.** Compare the field you changed against what you submitted, and score each edit applied, unchanged, or pending. If the applied share is under half, stop resubmitting. Crowdsourced edits from a non-owner are a low-yield channel, and the honest answer is often to claim the listing or to leave it.

### A duplicate-detection prompt may match on phone, not on identity

Adding locations of a multi-location business to a review directory, every location shared one central phone line. The duplicate check matched on that number and offered the parent company's existing record, a different name and a different category at only one of the addresses, under the phrasing "is this your business?"

Answering yes attaches your new location to an unrelated record. The correct answer is no, every time, and the field to compare is the address and name, never the phone.

Before starting a batch, count how many of your locations share a phone number. That count is exactly how many times you will see this prompt, and knowing it in advance stops it from feeling like a discovery each time.

### Record every dead source with its failure mode

A survey of data sources produced as much value from its negatives as its positives, but only because each negative recorded the exact failure. The modes need opposite responses:

| Mode | Response |
|---|---|
| `BLOCKED-ENVIRONMENTAL` | re-request from a second network egress before declaring it dead |
| `LICENCE` | it is permanently unusable for your purpose, stop evaluating it |
| `AUTH-GATED-ALTERNATIVE-EXISTS` | check whether the same data is elsewhere without the gate |
| `TOO-STALE` | usable, but only with a date attached to every claim |

One row per rejected source with the date and the verbatim failure. Without the mode, a future you re-tests all of them from scratch, and re-tests the ones that can never work.

## What to actually do this week

Three of these are worth an afternoon even if you do nothing else.

**Assert on the effective host** for every website URL a third party publishes about you. That is one `curl` in a loop and it finds the failure class no link checker reports.

**RDAP every domain** those records name. A 404 is urgent and it is a two-second check.

**Add a positive control** to any absence check you already run. If it cannot find something you know exists, it has been telling you nothing.

If you want the wider map of doing this across a portfolio of small sites without paying an agency to hold it, that is what I wrote **The $20 Dollar Agency** for.

## Related reading

**[When LLMs Get Your Brand Wrong](/blog/blog-tool-ai-hallucination-detector/)**: the downstream of everything here. These datasets are part of what models learned you from.

**[The check was green because it measured a state no visitor is ever in](/blog/blog-green-checks-that-cannot-fail/)**: the same failure shape from inside your own build, including the positive-control rule.

**[Why a Multi-Location Schema Audit Exists](/blog/blog-tool-multi-location-schema-audit/)**: your own structured data, which is where the `sameAs` worklist in Act 2 comes from.

**[Why Bing + Copilot + IndexNow Audit Exists](/blog/blog-tool-bing-places-audit/)**: the same audit posture aimed at a different set of consumers.

## Fact-check notes and sources

**RDAP answers the registry question, WHOIS answers a display question**: RDAP is the IETF-standardised successor to WHOIS and returns structured registration data, including an events array with registration and expiration. [RFC 7483, JSON Responses for RDAP](https://www.rfc-editor.org/rfc/rfc7483)

**`%{url_effective}` reports the last URL fetched when following redirects**: which is why it, not the status code, is the correct thing to assert against your canonical host. [curl manual, write-out variables](https://curl.se/docs/manpage.html#-w)

**Nominatim returns a class and type for every result**: the classification distinguishes a building-level match from a street-level one, and both carry coordinates of the same shape. [Nominatim developer documentation](https://nominatim.org/release-docs/latest/api/Output/)

**Overpass supports radius queries around a coordinate**: `node(around:radius,lat,lon)` enumerates by geometry rather than by name, which is the basis of the identity check in Act 2. [Overpass QL, the around filter](https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#Around)

**Structured data can declare other profiles for the same entity**: the `sameAs` property is the mechanism that makes the Act 2 set difference derivable from your own build output. [schema.org sameAs](https://schema.org/sameAs)

*This post is informational, not legal advice. The business described is anonymised and no client, brand, location, vertical or dataset record is identified. Mentions of third-party platforms and standards are nominative fair use and no affiliation is implied.*


---

Canonical HTML: https://jwatte.com/blog/blog-what-directories-believe-about-your-business/
RSS: https://jwatte.com/feed.xml
JSON Feed: https://jwatte.com/feed.json
Hero image: https://jwatte.com/images/blog-what-directories-believe-about-your-business.webp
