Your Canadian phone number is invalid

Canadian area code map — source: CNAC

If you try registering with eBay1, Shopify2, Twilio3, Intuit4, or Anthropic5 using a Canadian phone number with area code 2576 or 9427, most of them will tell you it doesn't exist.

The form field is very confident, and the form field is who you have to convince.

Area codes 257 (BC, May 24, 2025) and 942 (Ontario, April 26, 2025) are fully assigned, CRTC-approved, carrier-deployed Canadian area codes. Bell, Telus, and Rogers are issuing them. They route correctly. They receive SMS. They are, by every meaningful definition, real phone numbers.

They are also, according to a surprising number of major platforms, completely invalid. Not at the carrier level. Not in routing. On a web form.

$ curl -sX POST https://example.tld/signup \
    -d 'phone=+12575550123'

{"error":"invalid_phone","field":"phone","reason":"unknown_area_code"}

The crime scene

Here is the current Canadian numbering plan, expressed the way a validator actually sees it — one regex per province, matching the area codes carriers are actively issuing as of April 2026:

╔══════════════════════════════════════════════════════════════════════════╗
║  CA AREA CODES  ::  by province  ::  2026.04.28                          ║
╚══════════════════════════════════════════════════════════════════════════╝

  BC     ^(?:236|250|257|604|672|778)$              ← 257 added 2025
  AB     ^(?:368|403|587|780|825)$
  SK     ^(?:306|474|639)$                          ← 474 added 2025
  MB     ^(?:204|431|584)$
  ON     ^(?:226|249|289|343|365|382|416|437|519
            |548|613|647|683|705|742|807|905|942)$  ← 382, 742, 942 added 2025
  QC     ^(?:263|354|367|418|438|450|468|514|579
            |581|819|873)$                          ← 263, 354, 468 added 2025
  NB     ^(?:428|506)$                              ← 428 added 2025
  NS/PE  ^(?:782|902)$
  NL     ^(?:709|879)$
  YT     ^867$
  NT     ^867$
  NU     ^867$

 ── legend ──────────────────────────────────────────────────────────────────
 each pattern matches the three-digit area code segment of an E.164 number
 codes added 2025 are absent from libphonenumber bundles older than 9.0.7
 ────────────────────────────────────────────────────────────────────────────

The libraries don't actually express it this cleanly — the real patterns are minified into compressed alternation groups across all of NANP, optimized for parser speed rather than human readability. But this is the logical content. Each province has an allowlist. Each allowlist has to grow when the CRTC says it grows. If your bundle doesn't reflect the rows above, you are rejecting valid Canadian phone numbers right now.

Phone number validation, in every library worth using, is fundamentally an allowlist lookup against compiled metadata. The library does not ask the carrier whether +12575550123 is a real number. It cannot. It opens a bundled data file, finds the entry for country code 1 and country CA, walks a regex pattern compiled from the current set of approved Canadian area codes, and asks whether your number matches.

If the regex matches, valid. If it doesn't, invalid. There is no third branch. There is no "I don't know, let me ask someone." The metadata file is the source of truth, and the metadata file is whatever shipped in the version of the library you have on disk.

This is true across the ecosystem: Google's libphonenumber8, the JS port libphonenumber-js, the wrapper intl-tel-input9, the various PHP and Python ports, vendor-built validators that consume the same upstream metadata. They differ in API surface and bundle size. They do not differ on the fundamental: the validity of an area code is a property of the data file, not of the carrier network.

To make the failure concrete: in libphonenumber, the Canadian area code allowlist is an explicit alternation group in the country's regex pattern. For codes starting with 2, the pre-fix pattern reads roughly:

2(?:04|[23]6|[48]9|50|63)

Covering 204, 226, 236, 249, 289, 250, and 263. Notably absent: 257. Same story for 942 in the 9xx group. When your number hits validation, the regex fails to match, the library returns invalid, and the platform rejects you. No useful error. No explanation. Just a red outline on a phone field and a vague message about entering a valid number.

The fix is, at the data layer, adding two characters to a regex. Three if you count the alternation pipe. The patch landed upstream and shipped in libphonenumber ≥ 9.0.710. The fix flowed downstream into the wrapper most frontends use, intl-tel-input ≥ 25.3.29, which pulls in libphonenumber v9.0.11 with the corrected metadata. Both patches have been publicly available since mid-2025. Updating is a one-line dependency bump.

$ npm i libphonenumber-js@latest
$ npm i intl-tel-input@latest

That is the entire engineering effort required to allow your customers in British Columbia to give you their phone number.

This is the part developers reach for the "well, technically it's not the library's fault, the metadata was correct at release time" defense. Sure. The library is doing what it was designed to do: lookup against a compiled allowlist. The library will keep being right — for as long as the allowlist is current. The moment the world adds an area code, the library is wrong about that area code until the bundle is refreshed and re-deployed. That window is the entire bug class. Phone number metadata is not a one-time install. It is an ongoing subscription to changes in the numbering plan, and you are responsible for staying current on it for as long as you accept phone numbers from users.

And yet.

The receipt

We tested directly against the following platforms and confirmed each rejects one or both area codes as invalid: eBay, Shopify, Twilio, Intuit.

We reported the issue to all of them, along with Anthropic. As of publication, Anthropic is the only one that has acknowledged and shipped a fix. The others have, charitably, been quiet and incapable of demonstrating a will to understand the issue.

In some cases the rejection happens via a stale validation bundle served directly from a CDN, cached at a version that predates the fix. In others the same outdated metadata appears to be baked server-side as well, which means bypassing the frontend doesn't help.

Twilio's own public documentation11 still lists Canada as having 42 area codes. It has 43. That page is part of their developer-facing glossary for phone number infrastructure. Twilio sells phone number infrastructure. The irony is doing a lot of heavy lifting there.

It gets better. A major Canadian telecommunications provider — actively issuing numbers in these area codes to its own customers — fails to validate those same numbers on its own account signup form. You cannot register on their website using a phone number they sold you. The form rejects it. The same stale validation, the same undeployed fix, the same red outline. A carrier gatekeeping itself.

A Canadian federal government website responsible for import and export registration also fails. If you are a business operating in Canada with a 257 or 942 number and you need to register with that system, your own government's form will tell you your phone number is not a phone number. The CRTC approved the area code. A different arm of the federal government's web infrastructure has not received the memo, the second memo, or the email asking why nobody has read either memo.

Five platforms is what we got around to testing. The pattern, the dependency, and the failure mode are all generic. There is no reason to think this stops at the five we named. If you ship a phone-input field anywhere on the public internet and you have not refreshed your validation metadata since spring 2025, you are very likely on this list and just do not know it yet. Go check. We'll wait.

The dependency story underneath

The uncomfortable implication is not really about phone numbers.

A dependency as prominent as Google's phone validation library, with a public changelog that is not hard to follow, not making it into production for six-plus months at companies with nine-figure infrastructure budgets says something. It says the dependency update process at those companies is either not running, not watching this class of library, or not converting findings into deploys. That is the same process responsible for pulling in security patches, deprecation notices, and breaking change advisories across every other third-party dependency in the stack.

Phone number validation is a low-stakes canary. It breaks in an obvious, user-visible way. Nobody gets breached because 257 gets rejected. But if the team responsible for validating Canadian phone numbers at a major carrier doesn't know their own libphonenumber metadata is eight-plus months stale, it is reasonable to wonder what they also don't know about the rest of their dependency graph. A company that can't validate its own product is not a company running a tight ship on CVE triage12. Stale phone metadata and stale security patches come from the same place: a dependency update pipeline that nobody is watching.

// what the rejection actually looks like in libphonenumber-js
import { isValidPhoneNumber } from 'libphonenumber-js'

isValidPhoneNumber('+12575550123', 'CA')   // false  ← stale bundle
isValidPhoneNumber('+12575550123', 'CA')   // true   ← post-2025 metadata

Same call, same number, same library. The only difference is whether the metadata bundle in node_modules was last refreshed before or after May 2025. That is the entire bug. It is the kind of bug that should not survive a single dependency-update sprint, let alone an entire fiscal year.

What to do about it

For developers hitting this:

  • Bump libphonenumber-js to ≥ 9.0.7 and intl-tel-input to ≥ 25.3.2. That is the fix.
  • If your validation runs client-side, check the bundle that is actually being served. CDN caches lie. Hash the metadata file and read the date.
  • If you use Twilio Lookup13 to gate SMS flows, test explicitly with a 257 or 942 number against your current configuration. Do not assume.
  • If you ship a phone-input field anywhere, write a regression test that hits every Canadian area code added in the last 24 months. There will be more.

The fix exists. It shipped months ago. Deploying it is not the hard part. Realizing you need to is.

Dealing with support has turned into a sanity test. Poorly implemented AI support agents will confidently misroute the issue, ask you to clear your cache, and close the ticket. Human support teams, when you reach them, will not know what "regex" or "client-side versus server-side" means, let alone what libphonenumber is. The escalation path is real but it is not visible from the form they handed you.

For Canadians locked out of platforms: cite CRTC Decision 2023-13514, name the specific library (libphonenumber) and the version requirement (≥ 9.0.7), and ask for engineering escalation. Support tier one will not know what libphonenumber is. That is expected. Get past them.

Area code 27315 goes live in Eastern Quebec in February 2027. See you then. Bring a regex.


  1. eBay Canada — https://www.ebay.ca

  2. Shopify — https://www.shopify.com

  3. Twilio — https://www.twilio.com

  4. Intuit — https://www.intuit.com

  5. Anthropic — https://www.anthropic.com

  6. "New area code 257 to be introduced in British Columbia in 2025." Newswire, 2024. https://www.newswire.ca/news-releases/new-area-code-257-to-be-introduced-in-british-columbia-in-2025-801174266.html

  7. CRTC Decision 2023-135 (introduces 942 in Ontario). https://crtc.gc.ca/eng/archive/2023/2023-135.htm

  8. Google libphonenumber — https://github.com/google/libphonenumber

  9. intl-tel-input — https://github.com/jackocnr/intl-tel-input ↩2

  10. libphonenumber releases — https://github.com/google/libphonenumber/releases

  11. Twilio docs — North American area codes (still lists Canada at 42). https://www.twilio.com/docs/glossary/north-american-area-codes

  12. National Vulnerability Database — https://nvd.nist.gov/

  13. Twilio Lookup v2 API — https://www.twilio.com/docs/lookup/v2-api

  14. Telecom Decision CRTC 2023-135 — Approval of new area codes 257 and 942. https://crtc.gc.ca/eng/archive/2023/2023-135.htm

  15. North American Numbering Plan Administrator — https://www.nationalnanpa.com/

Not Nixing Nix

Lossy Wet Memory: A Crux

Unlike an elephant, among other things, people can be forgetful. Further, unlike an elephant, people made the mistake that is computers. Computers can be used to remember things for people but perhaps that's only because we've yet to find a good elephant-memory interface.

That's only sort of a "tongue-in-cheek" joke given that there are companies like FinalSpark progressing towards wetware computing using "organoids" derived from human stem cells. Give this paper a read if you want to be intrigued and disturbed.

If you've done any development or work with computers that requires creating and maintaining an "environment" very quickly you might forget how the state of your current environment has come to be. If not congratulations on being a literate elephant. Otherwise, unless you are regularly rebuilding your environment, determining exactly how it came to be is tedious.

To fix the problem let's consider abstracting a layer and create a reproducible environment with Nix. Thus begins a foray with Nix.

Factors for Limitation

The machine we're working on is a Debian flavor distro on WSL. If you are following along on WSL be sure to enable systemd in your /etc/wsl.conf

[boot]
systemd=true

Installing Nix, specifically the package manager, and reading the docs are left as an exercise to the reader.

Further for the sake of this posts' brevity the "environment" for this exercise will simply be tmux.

Declarative Shell Configuration

Assuming you've got your Nix installation and daemon working the initial inclination to create our "environment" might be a declarative shell environment via a shell.nix file containing...

let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11";
pkgs = import nixpkgs { config = {}; overlays = []; };
in

pkgs.mkShellNoCC {
packages = with pkgs; [
        tmux
    ];

shellHook = ''tmux'';
}

In the same directory run nix-shell and if all goes well it will spawn you into tmux session. The keen observer will notice that the "shell" presented in the tmux session is not a "nix-shell". This is good or bad depending on what the desired "environment" is.

Using shell.nix in this way is beneficial if you would like a different environment for different directories or projects. You can modify the shell.nix file to suit the project needs in many ways. If you proceed with declarative shells you might want to also setup automatic environment activation.

There are some flaws however with this method of reproducible environments. Namely what if two projects have essentially the same dependencies? It would be inefficient to instantiate a new shell for each. Also if we will be using tmux for everything we could consider that a user configuration and not necessarily a project level configuration. Specifying user configurations in each of the projects does not seem "appropriate." These considerations naturally lead to Nix Home Manager.



Management

"The future of all humanity comes down to one word... Management." - Bud Askins

Once you've installed home manager we'll be working with the config file in ~/.config/home-manager/home.nix

Add the pkgs.tmux to the home.packages and add

programs.tmux = {
        enable=true;
  };

Run home-manager build and if successful you will get no errors. Run home-manager switch to activate your user environment.

If we wanted to install the tmux plugins, before Nix, we could download install and then update our tmux config files to source the config. However Nix community has packaged these plugins and can be installed by updating the home.nix file.

programs.tmux = {
        enable=true;
        plugins = with pkgs;
                [
                  tmuxPlugins.better-mouse-mode
                  tmuxPlugins.dracula
                ];

  };

In Closing

We explore a minimal tangent of the capabilities Nix provides by looking at 2 ways of configuring an "environment." The Nix ecosystem is fast evolving and the "home-manager" is not considered a stable feature. If you are considering migrating all your dotfiles to the purview of Nix it will take some time and effort and to an extent you will be sacrificing some stability unless you explicitly specify the versions of packages and programs you will use with Nix. After all elephants are migratory.

How This Site Was Built

About This site

Here begins a journey into sharing some of my learnings.

Let's start the adventures with how this site was built. It's Zola hosted on Cloudflare Pages. It's free, easy, fast, and built with Rust.

I picked Zola as my static site generator primarily because I wanted to dip my toes in Rust and I wanted to write my blogs in Markdown. On the security aspect since it's hosted on Cloudflare the woes about DDos driving up costs disappear. It's also a static site with no moving parts thus no inputs = no outputs.

A Few Time Burners aka What I learned

  • Don't replace all :: with || in your zola blog. (Templates will fail to render and you learn about Rust paths)
  • Change the Clourflare build version to version 1. Do that here dash.cloudflare.com/{your_id_number}/pages/view/blog/settings/builds-deployments (Your build will fail if you use the default version 2)
  • Make sure the base_url in the config.toml has https:// (You will spend lots of time understanding how page.permalink is used in zola)
proudly Canadian