IndexNow lets you tell a search engine that a URL changed instead of waiting for it to come back and look. It is free, the client below is under thirty lines, and the obvious question — does it actually do anything? — is one almost nobody answers with numbers.

Over two days I submitted 4,589 URLs across five domains I run, in seven POSTs, and kept every HTTP receipt. This post is the receipts, the analytics split underneath them, and an honest account of why I still cannot tell you whether it worked.

What IndexNow actually is

You put a text file at your web root whose filename is a key (8 to 128 hexadecimal characters) and whose body is that same key, nothing else. Then you POST a small JSON document — {host, key, keyLocation, urlList} — to https://api.indexnow.org/indexnow listing up to 10,000 of your own URLs. Participating engines share submissions with each other — Microsoft Bing, Yandex, Seznam and Naver among them. Google is not on that list and does not participate, so nothing you submit through IndexNow reaches Google. That one fact decides most of what follows.

The recipe, in one file you can run

Read your sitemap, dedupe, refuse to send unless your own key file is live and says exactly the key, then POST. That refusal is the only interesting line in the program:

// announce.mjs — Node 18+ (global fetch). No dependencies.
const HOST = 'example.com';
const KEY = 'YOUR-32-CHAR-HEX-KEY-GOES-HERE';
const KEY_URL = `https://${HOST}/${KEY}.txt`; // body must be exactly the key
const SITEMAP = `https://${HOST}/sitemap.xml`;

const xml = await (await fetch(SITEMAP)).text();
const urls = [
  ...new Set([...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1].trim())),
].filter((u) => u.startsWith(`https://${HOST}/`));

// REFUSE TO SEND unless the key file is live and matches. Never skip this.
const probe = await fetch(KEY_URL);
const body = (await probe.text()).trim();
if (!probe.ok || body !== KEY) {
  throw new Error(`key file not live: HTTP ${probe.status}`);
}

for (let i = 0; i < urls.length; i += 5000) {
  const chunk = urls.slice(i, i + 5000);
  const res = await fetch('https://api.indexnow.org/indexnow', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json; charset=utf-8' },
    body: JSON.stringify({ host: HOST, key: KEY, keyLocation: KEY_URL, urlList: chunk }),
  });
  console.log(`POST ${chunk.length} URL(s) -> HTTP ${res.status}`);
}

Two details are load-bearing. The origin filter means a stray absolute URL in your sitemap can never be announced as yours. And the key probe is what stops the whole thing from failing silently: on one of my domains the key file was sitting in the repository while the live URL returned 404, lost in a redeploy, so every submission from that host would have been rejected with nothing to see. A client that checks first fails closed and tells you. A client that does not check just logs errors into the void.

Chunking at 5,000 keeps you well under the protocol’s 10,000-URL ceiling with room for the engines to change their minds about it.

Two things that will bite you on the first run. The <loc> loop reads one level of sitemap, so if /sitemap.xml is an index that points at child sitemaps — which is what a stock Astro or Next build produces — you will parse exactly one URL and announce nothing; fetch the children and concatenate. And point it at a real leaf sitemap to sanity-check the count before you send: against this site’s own, the script above prints 33, which is the number in the table below.

What the responses mean in practice

There are three you will see, and only one of them is a problem:

  • 200 — received, and the key was already validated.
  • 202 — received, key validation pending.
  • 403 SiteVerificationNotCompleted — the engine has not fetched your key file yet.

200 means received and the key was already validated; 202 means received with key validation pending. Both are success, and both are the whole receipt. A 403 SiteVerificationNotCompleted response means the engine has not fetched your key file yet, and it clears itself on a retry.

My receipts:

datehostURLsHTTP
2026-09-12weatherhack.com2,194403, then 200 on retry
2026-09-12weatherhack.com2,194200
2026-09-13weatherhack.com155200
2026-09-13dankbuild.com5202
2026-09-13dankdev.com33202
2026-09-13dankchess.com3202
2026-09-1333o1.com5202

The 403 is worth dwelling on because it looks exactly like a broken key and is not. At the moment it happened, the key file was live and correct: 200 to plain curl, 200 to a bingbot user-agent, 200 with no user-agent at all, served through Cloudflare with no challenge. A single-URL submission sent immediately afterwards returned 200, and the 2,194-URL batch then returned 200 as well. The first call appears to be what triggers verification. Every later first-ever submission on a fresh host returned 202 straight away, including one sent 34 minutes after its key file went live.

The success body is empty — 0 bytes — in every case. That is the entire output of a successful IndexNow submission: a status code, an empty body, and a timestamp. Hold that thought.

Now measure it. This is where it falls apart

To find out whether Bing did anything with those submissions you need Bing Webmaster Tools: crawl stats and index coverage for the host you submitted. I was not enrolled. None of the five domains were. So for the engines that actually consume IndexNow, my measurement resolution is not poor, it is zero — I cannot see a single crawl, a single indexed page, or a single change in either.

What I do have is Google Search Console, which reports on the one major engine that does not participate. For dankdev.com over the 2026-08-14 to 2026-09-11 window it reports 8 clicks and 420 impressions, a 1.9% clickthrough rate, and an average position of 17.5. Those numbers are real, and they are irrelevant to the question being asked, because Google never received any of this.

How big is Google here anyway?

Here is the split from my own first-party analytics for the 7 days ending 2026-09-13, whole estate, counting only referrals from search engines and AI assistants. Direct and no-referrer visits are excluded because they include me.

referrerreferrals
chatgpt.com111
duckduckgo.com91
Bing (including cn.bing.com)70
Yahoo53
Google8
Ecosia7
copilot.com6
Qwant2
Baidu2
Perplexity, Mojeek, Yandex, openai1 each
total354

Google is 8 of 354 — 2.3% of external discovery on these sites. Bing, Yahoo, DuckDuckGo and Ecosia together are 221 of 354, or 62.4%. Yahoo’s web results have long been served by Bing, DuckDuckGo’s traditional web links are Bing-sourced, and Ecosia has historically been Bing-backed, so that 62.4% is the slice IndexNow plausibly touches. I want to be precise about the word plausibly: a referrer string cannot tell you which index served a result. That grouping is an assumption about how those products are built, not something I measured.

So the instrument I own covers the 2.3% channel, and the 62.4% channel — the one I was actually submitting to — is the one I have no instrument for.

The absolute numbers are small and I would rather say so than dress them up. That is 354 search-and-assistant referrals in a week across every domain I run, against 343 direct or no-referrer visits in the same week. This site took 48 pageviews in those 7 days and has taken 353 since it started counting on 2026-06-17. At that scale a single good day moves a percentage point, and any effect smaller than a good day is invisible to me by construction.

Honest limits: what this cannot show

There is no control group. Every domain with a live key got submitted; I held nothing back, which was the mistake that makes everything below unavoidable.

Inventory moved inside the window. New pages shipped on these domains during the same days the submissions went out, so the URL set and the announcements changed together.

Seasonal demand is in the data. The largest site in the sample is a weather site, and its share of that 7-day referral list is overwhelmingly October and September city pages. Demand for those queries rises on a calendar whether or not anybody announces anything.

One estate, one operator, two days, and the counts above.

So: I am not claiming IndexNow moved traffic, rankings or indexing. There is no control group here, inventory changed inside the same window, the largest site in the sample serves seasonal query demand, and the instrument that would answer the question was never installed. What I can show is that seven submissions were accepted. That is all a status code proves, and it is all I have.

One piece of evidence does cut against getting excited. A small site of mine was announced on 2026-07-17 with 7 URLs in its sitemap and has 38 pageviews to its name, all-time. Over the same two months the weather site earned 619 referrals — and it had not been announced at all; its first submission was 2026-09-12, at the end of that period. I cannot attribute those 619 to any cause either. But whatever produced them, it was not an IndexNow submission, because there had not been one.

The transferable lesson: pick the instrument first

Wiring up IndexNow is an afternoon. Being able to answer did it do anything is a different project, and it has to start earlier. Before your first submission:

  • Create a Bing Webmaster Tools account and verify every host you plan to submit. That is where crawl and index-coverage data lives for the engines that actually consume IndexNow. Yandex Webmaster and Naver Search Advisor too, if those markets matter to you.
  • Write down a baseline first: indexed page count, crawl requests per day, impressions. A number you did not record before the change is a number you cannot use after it.
  • If you have more than one comparable site, hold one back for a month. A control group costs nothing and is the only thing that converts a story into a result.

I did none of that, which is why this post is called what I could measure. To learn whether Bing did anything with a submission you need Bing Webmaster Tools, and if you are not enrolled before you submit, your measurement resolution for the engines that actually consume IndexNow is zero. That generalises well past this protocol: a change you make for search is worth exactly as much as your ability to detect its effect, and the detection has to be installed first.

Would I wire it up again? Yes. It costs an afternoon once, the protocol is genuinely simple, the failure mode is a status code rather than a silent penalty, and a correct client refuses to send when your key is missing. Just don’t confuse submitted with indexed — and don’t do what I did and measure last.

← Back to all posts

Follow new posts by RSS: paste dankdev.com/feed.xml into any feed reader. It is a plain XML file — no signup, no email address, no account. Posts land there the same day they go up, and there is nothing to unsubscribe from later.

Back to the top