Two rows on the Mega Analyzer's Trust + Conversion completeness card read the same phone numbers three different ways. Visible phone numbers are tap-to-call (N) turns into N of N visible phone number(s) not wrapped in a tel: link (208-555-0100) when a number printed on the page has no tel: link anywhere that dials the same digits. Phone numbers agree (JSON-LD, tel: links, visible text) turns into Phone numbers disagree: JSON-LD 208-555-0100 vs tel: 208-555-0101 when the three sources stop naming the same line. What the pair is really measuring is whether the number a person reads, the number their phone dials and the number you told Google about are one number.
What the check actually tests
The analyzer builds three lists from the fetched HTML and reduces every entry to digits before comparing anything.
Visible numbers. It clones the body, strips script, style, noscript and template, and runs one regular expression over the remaining text. Two shapes match. The North American shape is an optional +1 or 1, an area code that starts with 2 through 9 (parentheses allowed), a separator (space, dot or hyphen), three digits, a separator, four digits. The international shape starts with + and a one to three digit country code, then three more digit groups of two to four digits each, separators optional. So (208) 555-0100, 208-555-0100, 208.555.0100, 208 555 0100, +1 208 555 0100, +12085550100 and +44 20 7946 0958 all count. Three things do not: a bare 2085550100 with no separators at all, a seven-digit number with no area code, and a national format outside North America such as 020 7946 0958 with no +44 in front. The fourteen characters before each match are checked for the word "fax" in any letter case, and a match with "fax" in front of it is dropped. Each survivor is normalized: every non-digit removed, and a leading 1 stripped when the digit string is exactly eleven digits long. Anything shorter than ten digits is discarded, and duplicates collapse to one entry.
tel: links. Every <a> whose href starts with tel: is collected, the part after the scheme is percent-decoded, and both the href and the link's own text are normalized the same way. The set of distinct href digit strings is the second list.
JSON-LD telephone. The walker visits every object in every JSON-LD block on the page, at any depth, and collects every telephone value it finds, string or array: on the LocalBusiness, on a nested contactPoint, on a Person, on the publisher Organization. Those are normalized, and the ones with ten or more digits are the third list.
The tap-to-call row renders only when the visible list is not empty. It passes when every visible number appears in the tel: set. That is set membership across the whole page, not a per-occurrence test: a number printed as plain text in the header passes as long as the same digits sit inside a tel: link in the footer. Whether it renders as a pass/fail row (a red ✗ on a miss) or as an information row (a blue ℹ that does not count against the fail total) depends on one gate: the page's business vertical starts with Local- or YMYL-, or the JSON-LD contains at least one node typed Organization, Corporation, NGO, MedicalOrganization, EducationalOrganization or any member of the LocalBusiness family. A publisher Organization node is enough to flip it, so most sites that ship a full @graph get the red version. The fail detail reads "On a phone a plain number has to be copied by hand," shows the wrapped markup for the first unlinked number (always with a +1 prefix, even for a non-NANP number), and closes with "Half of local-business traffic is mobile and the call is the conversion."
The parity row renders only when the JSON-LD list is not empty or at least one tel: link is a mismatch. With no JSON-LD telephone and clean links there is no parity row at all, pass or fail. When it renders, it collects up to three problems and joins them with semicolons:
- "JSON-LD X vs tel: Y" when both lists have entries and no JSON-LD number appears in the
tel:set. One shared number satisfies it, so a JSON-LD carrying two locations' numbers passes as long as one of them is dialable on the page. - "JSON-LD X is not shown on the page" when the JSON-LD list and the visible list are both non-empty and share nothing. If the visible list is empty because the number lives only in an image, this problem never fires.
- "N tel: link(s) dial a different number than they show" when a
tel:link's text contains ten or more digits and those digits differ from thehrefdigits. Link text such as "Call us" or an icon has fewer than ten digits and is never compared.
Because every comparison is digits-only, formatting never trips it: tel:208-555-0100, tel:+1-208-555-0100, tel:+12085550100 and even tel:+1 208 555 0100 (which RFC 3966 forbids) all reduce to the same key. The stricter formatting checks live in the Click to Call Audit, which also skips elements hidden with the hidden attribute or an inline display:none; the Mega Analyzer counts a number inside a display:none mobile drawer as visible, because it reads text content, not layout.
One trap I hit while running the normalizer against test strings: an extension in the href breaks both rows. tel:+12085550100;ext=12 and tel:+12085550100,,12 both reduce to thirteen digits, which is not eleven, so the leading 1 is kept and the key becomes 1208555010012. The visible (208) 555-0100 no longer matches any tel: key, so it is reported as unlinked, and if the link text shows the number it counts as a mismatch as well. The extension syntax is legal under RFC 3966 section 5.3; the analyzer just does not parse it.
Why it matters
Google's own description of how it indexes: "Google uses the mobile version of a site's content, crawled with the smartphone agent, for indexing and ranking." The page Google evaluates is the phone version, and on a phone the difference between a linked number and a plain one is the difference between a tap and a copy-paste. MDN describes what a tel: link does by device: "Cellular devices autodial the number," most operating systems have programs that can make calls, and other behaviors include saving the number to contacts or sending it to another device. A plain number offers none of that. The row's own detail text claims half of local-business traffic is mobile; I have no source for that figure, so act on the narrower sourced statement above.
The mismatch problem is the more expensive one because nobody notices it. A visitor reads (208) 555-0100, taps, and the href dials 2085550101 because someone updated the text after a line change and never touched the attribute. The call goes to a dead line or to whoever holds that number now, and the visitor blames you. Analytics will not show it: the tap registers as an event, the call just never arrives.
The JSON-LD disagreement costs you with Google rather than with the caller. Google's structured data policies say "Your structured data must be a true representation of the page content" and "Don't mark up content that is not visible to readers of the page." A telephone that appears nowhere in the visible text is exactly that. On the local side, the Local Business structured data guide describes telephone as "A business phone number meant to be the primary contact method for customers. Be sure to include the country code and area code in the phone number." The Business Profile guidelines ask you to "Provide a phone number that connects to your individual business location," to "Use a local phone number instead of a central call center helpline number whenever possible," and state that "The phone number must be under the direct control of the business" and "Do not provide phone numbers or URLs that redirect or 'refer' users to landing pages or phone numbers other than those of the actual business." A site whose JSON-LD says one number, whose tel: link dials a second and whose Business Profile lists a third hands Google three candidates for the primary contact method and no way to pick.
How to fix it
Step 1: find every number. Run the Click to Call Audit on the page first. It lists every visible number with its location in the document, every tel: link with its href, and the JSON-LD telephone side by side, and it emits the exact replacement markup for each unlinked number. That is faster than searching the template by hand.
Step 2: wrap each visible number, E.164 in the href, formatting unchanged in the text. RFC 3966 says "Globally unique numbers are identified by the leading '+' character" and "All phone numbers MUST use the global form unless they cannot be represented as such." Hyphens, dots and parentheses are allowed inside the URI as visual separators that "merely aid readability," but spaces are not: "'tel' URIs MUST NOT use spaces in visual separators." The plus-and-digits form is the one every dialer accepts, so use it in the attribute and keep the human formatting in the link text. This is the shape the Click to Call Audit emits:
<a href="tel:+12085550100">(208) 555-0100</a>
Do it for every occurrence, not just the first. The Mega Analyzer's row passes on the first one because it tests set membership, but the person reading the footer on their phone is not consulting the header.
Step 3: put a call button in the header. The Click to Call Audit warns when no tel: link sits inside the header or navigation (<header>, [role=banner], .site-header, a <nav> outside the footer) or within the first 15 percent of the page's elements, because a phone visitor should not have to scroll to call. The button its AI fix prompt emits, placed inside the header:
<header>
<a class="call-btn" href="tel:+12085550100" aria-label="Call (208) 555-0100">Call (208) 555-0100</a>
</header>
Keep the digits in the button text. A button that reads only "Call" passes the tap-to-call row, since its text is never compared, but the Click to Call Audit flags a label-only or icon-only link when its number appears nowhere in the page text, and a screen reader user gets the word "Call" and nothing to write down.
Step 4: make the JSON-LD telephone the same digits. Use the same E.164 string you put after tel:. schema.org types telephone as plain Text, so +1-208-555-0100 is accepted too, and the analyzer reduces both to the same key, but one string everywhere is the version nobody has to think about. If a second line exists (a reservations desk, a second location), list it under contactPoint rather than dropping it, so the parity row finds at least one match on each side.
{
"@context": "https://schema.org",
"@type": "Motel",
"@id": "https://example.com/#localbusiness",
"name": "Example Motel",
"url": "https://example.com/",
"telephone": "+12085550100",
"contactPoint": [
{
"@type": "ContactPoint",
"contactType": "reservations",
"telephone": "+12085550102"
}
],
"address": {
"@type": "PostalAddress",
"streetAddress": "123 East Main St",
"addressLocality": "Twin Falls",
"addressRegion": "ID",
"postalCode": "83301",
"addressCountry": "US"
}
}
Step 5: handle extensions and fax lines so they stop tripping the rows. Put the extension outside the link and dial the main line: <a href="tel:+12085550100">(208) 555-0100</a> ext. 12. Label a fax number with the word "Fax" right before it, within fourteen characters, or the analyzer counts it as an unlinked visible number and asks you to make it tappable.
Step 6: offer a text path. RFC 5724 defines the sms: scheme with the same global number form and an optional body field. The Click to Call Audit treats this as optional and reports it at information level, but a prefilled body tells you which page the message came from. RFC 5724's own example is sms:+15105550101?body=hello%20there; the ?&body= spelling below is the cross-platform variant the Click to Call Audit emits:
<a href="sms:+12085550100?&body=Hi%2C%20I%20have%20a%20question">Text us</a>
The Click to Call Audit also looks for a WhatsApp link, common for guests messaging from outside the US, and suggests the https://wa.me/12085550100 form: country code plus digits, no plus sign.
Step 7: verify all three surfaces in one pass. Re-run the Mega Analyzer for the two rows, then the Click to Call Audit for the formatting and above-the-fold checks it adds. Then paste the site's number next to the Google Business Profile, Apple, Bing, Yelp and Facebook versions into NAP Consistency, because the parity row can only compare what is on the page; the listing is the fourth copy it never sees. If someone else maintains the template, the Code-Diff Patch Generator writes before and after patches for the SEO, performance and accessibility issues it detects on a fetched page. It does not detect tel: gaps itself, but that before/after block is the format to hand a developer for the tel: change too.
When to leave it alone
If the tap-to-call row is blue, the page is not classified local-like and carries no business schema, and the analyzer is telling you the link is optional. On an author site or a documentation page that prints a number once in the footer, wrapping it is still a two-second edit that never hurts, so I would do it anyway. The blue is a statement about severity, not correctness.
Numbers that are not yours to dial are the real exception. A page that quotes a state agency's hotline, a supplier's number in a vendor list, or a fictional (208) 555-0100 in a code sample will show up as visible numbers, and on a local business page they will be counted as unlinked. Wrapping a third party's number in a tel: link is not wrong, but it is not your conversion either. If the page exists to route calls to other people (a directory, a resource list), either accept the row or move those numbers off the page that carries your LocalBusiness schema.
Direct-dial extensions through a pause (,,12) and ;ext= parameters are legitimate RFC 3966 syntax that the analyzer's normalizer misreads, as described above. If the switchboard cannot route a call without the extension and you want the phone to dial it, keep the link and take the red row; the finding is the tool's limitation, not yours. If the main line answers, use the step 5 form instead.
Non-NANP numbers in national format (020 7946 0958) are invisible to the visible-number pattern, so a UK or EU page that prints its number that way gets no tap-to-call row at all (the row only renders when the visible list is non-empty) and can still pass the parity row with no tappable number anywhere. That is a blind spot, not a pass. Write the number with its country code (+44 20 7946 0958) and the pattern sees it; the Click to Call Audit also recognizes a national-format number inside a tel: link whose href carries the country code.
Picture a twelve-property operator with one template and one tel: attribute hard-coded in it: the number in the attribute belongs to the first property, and every other site prints its own number in the text and dials the wrong motel. That is the mismatch problem at scale, and no amount of per-page patching fixes it. One number per site, kept in a single data field that renders into the text, the href and the JSON-LD together, is the pattern The $100 Network is built around, and it is the only structural fix for that class of bug.
Fact-check notes and sources
- Source: https://www.rfc-editor.org/rfc/rfc3966 defines the
telURI: global numbers identified by a leading "+" and composed per E.164 (section 5.1.4), the MUST on global form (section 5.1), the visual separators "-", ".", "(" and ")" that "merely aid readability" and the MUST NOT on spaces (section 5.1.1), the;ext=extension parameter (section 5.3), and the exampletel:+1-201-555-0123(section 6). - Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#linking_to_telephone_numbers establishes
tel:behavior by device: "Cellular devices autodial the number," most operating systems have programs that can make calls, and other behaviors include saving the number to contacts or sending it to another device. - Source: https://support.google.com/business/answer/3038177 (Guidelines for representing your business on Google) establishes the phone rules quoted above: a number that connects to the individual location, a local number over a call center, direct control by the business, and no numbers that redirect or refer elsewhere.
- Source: https://developers.google.com/search/docs/appearance/structured-data/local-business establishes that
telephoneis a recommended (not required) LocalBusiness property described as "A business phone number meant to be the primary contact method for customers," with the instruction to include the country code and area code. - Source: https://developers.google.com/search/docs/appearance/structured-data/sd-policies establishes "Your structured data must be a true representation of the page content" and "Don't mark up content that is not visible to readers of the page."
- Source: https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing establishes "Google uses the mobile version of a site's content, crawled with the smartphone agent, for indexing and ranking."
- Source: https://www.rfc-editor.org/rfc/rfc5724 defines the
smsURI scheme, the recipient in RFC 3966 global form, thebodyfield (the ABNFsms-fields = sms-field *( "&" sms-field )has no leading&), and the examplesms:+15105550101?body=hello%20there. - Source: https://schema.org/telephone establishes that
telephonehas the expected type Text, so any string is accepted by the vocabulary itself.
Related reading
- A way to reach you, in plain text: the contact signals the Mega Analyzer looks for
- NAP Consistency for Local Rank: Why "Street" vs "St." Costs You Map-Pack Position
- Your Google Maps Listing Decides The Phone Call. Audit It.
- Most forms fail accessibility before anyone ever fills them out
This post is informational, not legal advice. Mentions of third parties are nominative fair use. No affiliation is implied.