What Is IndexNow and How Do You Implement It in 2026?

IndexNow turns indexing from a wait into a push: one HTTP request tells Bing, Yandex, Naver, Seznam, Yep, Amazon and the Internet Archive that a URL changed. By the end of this page you will have the key file rules, the exact endpoints, and submissions firing from your own publish pipeline.

CategoryCrawler access, licensing & indexing StatusStable Maintained byMicrosoft, Yandex and partners Glippy checkMachine Readability (category 6)

IndexNow (IndexNow push indexing) is an open protocol for telling search engines that a URL on your site has been added, updated or deleted, instead of waiting for a crawler to come back and notice. You prove ownership by hosting a text key file on the host, then send either a single-URL GET or a JSON POST carrying up to 10,000 URLs; every engine that adopts the protocol agrees to share what it receives with the other participants. It is built for sites whose content changes faster than a crawl cycle: publishers, e-commerce catalogues, job boards, docs and pricing pages.

Why IndexNow matters for AI visibility

Answer engines answer from an index. The gap between the moment you publish and the moment a URL exists in a searchable index is dead time during which an assistant will happily cite your old price, your retired product or a competitor. IndexNow collapses that gap to a single HTTP request. The registry at indexnow.org/searchengines.json currently lists Microsoft Bing, Yandex, Seznam.cz, Naver, Yep, Amazon's amazonbot and the Internet Archive, and a submission to any one of those endpoints is shared with the rest. Bing's index in particular is the retrieval layer behind Microsoft Copilot and, at least in part, behind ChatGPT search, so pushing there has a direct line into AI answers.

Be clear about what IndexNow is not. It is a search-index mechanism, not an AI ingestion pipe. Google does not support IndexNow and never adopted it after evaluating it in 2021, so Google's index, and therefore AI Overviews and Gemini's grounding, is unaffected by anything you push. Assistants that run their own crawlers on their own schedules, including Perplexity, OpenAI's OAI-SearchBot and Anthropic's ClaudeBot, will not see your submission either. IndexNow also does not guarantee indexing: a submitted URL still goes through the engine's crawl quota, scheduling and quality checks, and an HTTP 200 only confirms the submission was received. What you get for one request per change is the removal of crawl latency on the indexes that do accept a push, which is a cheap win rather than a strategy.

Where the spec lives

IndexNow is a short protocol and the authoritative text is all on indexnow.org. These are the pages worth keeping open while you wire it up.

  • IndexNow protocol documentation - the normative text: single-URL GET format, bulk POST JSON schema, key character rules, the two ownership options and the full response code table. Start here.
  • IndexNow FAQ - the operational rules that are not in the protocol text: rate limit behaviour, Retry-After handling, subdomain and multilingual guidance, and what to do about deleted or redirected URLs.
  • searchengines.json registry - the live list of participating engines. Each entry points at a meta.json holding that engine's api endpoint, verifier IP ranges and public keys. Fetch it rather than hardcoding a list.
  • Bing Webmaster Tools: add IndexNow to your website - Microsoft's walkthrough plus an in-page key generator, useful when you want a key without writing a script.
  • Cloudflare Crawler Hints - the zero-code edge integration. Cloudflare derives change signals from cache misses and forwards them to IndexNow on your behalf, on every plan tier.
  • Official IndexNow plugin for WordPress - maintained by the Bing Webmaster Tools team. Generates and hosts the key, fires on publish, update and delete, and honours noindex. Reach for this before writing anything yourself on WordPress.

Three ways to implement IndexNow

The three approaches differ in where the trigger lives. Run the curl script by hand when you are launching, migrating or backfilling a batch and want to see the raw response codes. Put the publish hook in your CMS or headless backend when you own the code that saves content, because that is the only place that knows what genuinely changed. Run the edge Worker when you cannot touch the application at all and have to infer changes from the sitemap.

01

Key file plus a one-shot bulk push with curl

Use this the first time you set IndexNow up, and after a migration or relaunch when a large set of URLs changed at once. It covers all three moving parts: generating a key, proving ownership, and both request forms.

bashscripts/indexnow-bulk.sh
# 1. Generate a key: 8 to 128 chars, only a-z A-Z 0-9 and dashes. Publish it at the host root.
KEY=$(uuidgen | tr 'A-Z' 'a-z' | tr -d '-')
printf '%s' "$KEY" > "public/$KEY.txt"

# Deploy, then confirm the file is public, plain text and contains only the key.
curl -s -w '%{http_code} %{content_type}\n' "https://www.example.com/$KEY.txt"

# 2. Smoke test with the single-URL GET form. The url value must be RFC-3986 encoded.
curl -s -o /dev/null -w 'GET %{http_code}\n' \
  "https://api.indexnow.org/indexnow?url=https%3A%2F%2Fwww.example.com%2Fpricing&key=$KEY"

# 3. Push the batch. 10,000 URLs per POST is the protocol ceiling.
cat > indexnow.json <<JSON
{
  "host": "www.example.com",
  "key": "$KEY",
  "keyLocation": "https://www.example.com/$KEY.txt",
  "urlList": [
    "https://www.example.com/pricing",
    "https://www.example.com/guides/geo-checklist",
    "https://www.example.com/blog/retired-post"
  ]
}
JSON

curl -sS -w '\nPOST %{http_code}\n' -X POST 'https://api.indexnow.org/indexnow' \
  -H 'Content-Type: application/json; charset=utf-8' \
  --data-binary @indexnow.json

# 200 received | 202 received, key validation pending | 403 bad key
# 400 malformed | 422 URL outside the host or key scope | 429 back off

What this does: api.indexnow.org is the shared endpoint that fans your submission out to every participating engine, so you only need one request per batch. A 202 on the very first call is normal and means the engine has queued a fetch of your key file before it trusts the submission.

02

A publish hook in your CMS or headless backend

This is the version that should end up in production on a custom stack. The application already knows which entry was saved, unpublished or deleted, so it is the only place that can submit exactly what changed. On WordPress, install the official plugin instead of porting this.

javascriptlib/indexnow.js
// Call notifyChanged() from your publish, update and unpublish hooks.
// Deletions matter too: submit URLs that now return 404, 410 or a 301.
const ENDPOINT = 'https://api.indexnow.org/indexnow';
const HOST = 'www.example.com';
const KEY = process.env.INDEXNOW_KEY;
const MIN_RESUBMIT_MS = 5 * 60 * 1000; // indexnow.org: wait 5+ minutes per URL
const lastSent = new Map();
let queue = new Set();
let timer = null;

export function notifyChanged(urls) {
  const now = Date.now();
  for (const url of [].concat(urls)) {
    if (now - (lastSent.get(url) || 0) < MIN_RESUBMIT_MS) continue;
    queue.add(url); // must be the canonical, absolute, RFC-3986 encoded URL
  }
  if (queue.size && !timer) timer = setTimeout(flush, 10000); // coalesce bursts
}

async function flush() {
  timer = null;
  const batch = [...queue];
  const urlList = batch.slice(0, 10000);
  queue = new Set(batch.slice(10000));
  if (urlList.length === 0) return;

  const headers = { 'Content-Type': 'application/json; charset=utf-8' };
  const body = JSON.stringify({ host: HOST, key: KEY, keyLocation: `https://${HOST}/${KEY}.txt`, urlList });
  const res = await fetch(ENDPOINT, { method: 'POST', headers, body });

  if (res.status === 200 || res.status === 202) {
    const now = Date.now();
    for (const url of urlList) lastSent.set(url, now);
  } else if (res.status === 429) {
    for (const url of urlList) queue.add(url);
    timer = setTimeout(flush, Number(res.headers.get('retry-after') || 60) * 1000);
  }
  console.log('[indexnow]', res.status, urlList.length, 'urls');
  if (queue.size && !timer) timer = setTimeout(flush, 10000);
}

What this does: the per-URL cooldown and the 10 second coalescing window stop a burst of editorial saves from turning into a burst of near-identical submissions, which is what earns a 422 or a 429. Everything else is fire and forget: the engine decides when to crawl, and the log line is your only record, so keep it.

03

An edge Worker that diffs the sitemap on a cron

For platforms you cannot add code to: a hosted store, a legacy CMS, a site owned by another team. If your sitemap carries honest lastmod values, the edge can work out what changed without the application knowing anything about IndexNow. On Cloudflare you can also just switch on Crawler Hints under Caching, Configuration for a zero-code version driven by cache misses.

javascriptsrc/worker.js
// wrangler.jsonc: { "triggers": { "crons": ["*/15 * * * *"] } }
// Bindings: KV namespace SEEN, secret INDEXNOW_KEY.
const HOST = 'www.example.com';

export default {
  async scheduled(controller, env, ctx) {
    const res = await fetch(`https://${HOST}/sitemap.xml`, { cf: { cacheTtl: 0 } });
    const xml = await res.text();

    const entries = [...xml.matchAll(/<url>[\s\S]*?<\/url>/g)]
      .map((m) => ({
        loc: (m[0].match(/<loc>([\s\S]*?)<\/loc>/) || [])[1],
        lastmod: (m[0].match(/<lastmod>([\s\S]*?)<\/lastmod>/) || [])[1] || ''
      }))
      .filter((e) => e.loc);

    const changed = [];
    for (const e of entries) {
      if ((await env.SEEN.get(e.loc)) !== e.lastmod) changed.push(e);
    }
    if (changed.length === 0) return;

    const push = await fetch('https://api.indexnow.org/indexnow', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json; charset=utf-8' },
      body: JSON.stringify({
        host: HOST,
        key: env.INDEXNOW_KEY,
        keyLocation: `https://${HOST}/${env.INDEXNOW_KEY}.txt`,
        urlList: changed.slice(0, 10000).map((e) => e.loc)
      })
    });

    // Only record the new lastmod once the submission was actually accepted.
    if (push.status === 200 || push.status === 202) {
      for (const e of changed.slice(0, 10000)) ctx.waitUntil(env.SEEN.put(e.loc, e.lastmod));
    }
    console.log('indexnow', push.status, changed.length);
  }
};

What this does: the KV store is the memory that makes the diff possible, and writing to it only on a 200 or 202 means a rate-limited run retries the same URLs on the next tick instead of silently dropping them. The key file still has to live on the site itself, because ownership is proved against the host you are submitting URLs for.

Implementation guidelines

The protocol is small, so almost every failure in production comes from one of these seven things.

  1. Put the key file at the host root. A key file at /{key}.txt authorises the whole host. If you use the keyLocation option instead, the file's folder becomes the boundary: a key at /catalog/key.txt can only submit URLs under /catalog/, and anything outside that comes back as a 422.
  2. Submit only what changed. IndexNow is for URLs that were added, updated or deleted since you switched it on. Do not walk your whole site into it, and do not backfill historic changes; that is what an XML sitemap with accurate lastmod values is for. A full-site push is only reasonable straight after a migration or redesign.
  3. Debounce per URL, and honour Retry-After. Wait at least five minutes before resubmitting the same URL unless the change is substantial. On a 429, pause for the interval in the Retry-After header and shrink the batch rather than retrying immediately.
  4. Push the canonical URL, one per language variant. Strip tracking parameters, resolve to the canonical, and encode per RFC-3986. Submit each hreflang variant as its own URL. Every subdomain is a separate host with its own key file and its own submissions.
  5. Submit removals and redirects, not just publishes. URLs that now return 404 or 410, and URLs you have 301'd away, are exactly the ones an assistant is most likely to be citing stale. Pushing them is how the index finds out.
  6. Log the status code on every submission. 202 is normal on first use and means key validation is pending. 403 means the key file was not found or does not contain the key. 422 means the URL is outside the host or the key's folder scope. Without a log you will not notice a key file that a deploy quietly stopped serving.
  7. Rotate the key by adding, not swapping. Publish the new {key}.txt first, start submitting with the new key, and only remove the old file once in-flight submissions have drained. Engines re-verify on the next submission, so there is no separate rotation call.

Do this, not that

Do

  • Serve {key}.txt as UTF-8 text/plain at the host root, containing the key and nothing else, with no trailing markup.
  • Fire submissions from the publish, update and unpublish hooks so the trigger is a real content change.
  • Keep every POST at or under 10,000 URLs and read Retry-After when you get a 429.
  • Pair IndexNow with an XML sitemap carrying accurate lastmod values, which is what Google and the long tail still use.

Do not

  • Put the key file behind a login, a WAF challenge, an IP allowlist, or a redirect to a prettier URL.
  • Push a URL that is noindex, disallowed in robots.txt, or not live yet: it spends crawl quota and gets dropped.
  • Resubmit the same URL every few minutes for cosmetic or layout-only edits.
  • Read an HTTP 200 as confirmation of indexing. It confirms receipt of the submission and nothing more.

How Glippy checks this

Glippy scores IndexNow under Machine Readability (category 6). Because the key file uses a deliberately unguessable filename, there is no way to fingerprint an IndexNow setup from the outside, so Glippy looks for explicit evidence instead: a reference to api.indexnow.org or IndexNow in the page HTML or robots.txt, or a linked key file, which scores as a pass. If it finds no reference but the response headers show the host sits behind Cloudflare, it reports that Crawler Hints can be toggled on for a one-click integration. Otherwise it returns an informational check rather than a penalty, since an absent reference is not proof that nothing is being pushed.

Check your IndexNow setup

Glippy runs 240+ checks across 16 categories on any page, including Machine Readability (category 6). No sign-up required.

Frequently asked questions

No. Google said in late 2021 that it would evaluate IndexNow and has never adopted it for general web indexing, so nothing you push reaches Google's index or the AI surfaces built on it. For Google you still rely on an XML sitemap with accurate lastmod values, internal linking, and Search Console's URL inspection tool. Running IndexNow alongside those costs nothing and does not affect how Google crawls you.

The registry at indexnow.org/searchengines.json lists Microsoft Bing, Yandex, Seznam.cz, Naver, Yep, Amazon's amazonbot and the Internet Archive. Engines that adopt the protocol agree to share submissions with each other, so you send to one endpoint, not seven. https://api.indexnow.org/indexnow is the shared endpoint that does the fan-out; each engine also exposes its own, for example https://www.bing.com/indexnow. Fetch the registry rather than hardcoding the list, because it changes.

No. IndexNow notifies search engines, and AI assistants only benefit where they retrieve from one of those indexes. Microsoft Copilot and, at least in part, ChatGPT search draw on Bing, which makes a push to Bing worthwhile. Assistants that run their own crawlers, including Perplexity, OpenAI's OAI-SearchBot and Anthropic's ClaudeBot, fetch pages on their own schedules and will not see your submission at all.

A 403 means the key was rejected: the key file was not reachable at the location you gave, or it was found but does not contain the key. A 422 means the request parsed but the URLs do not belong to the host, or they fall outside the folder that your keyLocation file covers, or you are resubmitting unchanged URLs. A 429 means you are over that engine's rate limit, so pause for the interval in the Retry-After header and reduce your batch size. A 202 is not an error: it means the URL was received and key validation is still pending, which is normal on your first submission.

Reviewed against the primary sources on . These standards move quickly, so check the linked specs before you ship.

Check your site for AI search readiness

Start free with the Glippy Chrome extension for instant page checks. Scaling up? Automate audits across many URLs and your whole sitemap with the Glippy MCP server.

Add Glippy to Chrome – free Automate with the MCP server →