IndexNow setup illustration showing webpage emitting ping signal radiating to multiple search engine nodes

IndexNow Setup Tutorial: Ping Search Engines on Every Publish

Pinging Bing, Yandex, and the IndexNow aggregators on every publish drops new URLs into their indexes within minutes instead of days. It is the cheapest, most underutilized SEO win in 2026: 30 minutes of work, then it compounds on every publish forever.

I am Ignacio (IGNAX), Spain-born and Paraguay-based, a solo full-stack developer and SEO/AEO specialist. For the deployment context see Cloudflare Pages SEO setup; the broader publish pipeline is part of the content engine service.

What is IndexNow, in one paragraph?

IndexNow is a simple HTTP API that lets you notify search engines of new or updated URLs on your site. You generate a random key, host it as a text file at your domain root for verification, then POST a JSON payload to api.indexnow.org with the URLs you want crawled. Participating engines (Bing, Yandex, Seznam, Naver, multiple aggregators) check the URLs and crawl them within minutes. The protocol is published openly at indexnow.org and supported by Cloudflare as a one-click add-on. IndexNow API request flow diagram from publish event through endpoint to Bing and Yandex search engines

What is the short setup checklist?

  1. Generate a random key (32–128 chars, alphanumeric).
  2. Host the key file at https://yourdomain.com/<key>.txt with the key as its only content.
  3. Write the ping function that POSTs to api.indexnow.org with your URL list.
  4. Trigger the ping on publish: from your CI, your deploy hook, or a manual button.
  5. Verify with a test publish: check Bing Webmaster Tools URL Inspection shows the URL crawled within an hour.

That is the entire protocol. The SvelteKit on Cloudflare Pages implementation follows.

How do I generate the key?

Any cryptographically random string between 8 and 128 alphanumeric characters works. From the command line:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Or:

openssl rand -hex 32

Save the output to .env and .env.example:

INDEXNOW_KEY=89bee770fc3a480394ff490cd251245a

The key is not sensitive. It will be public via the txt file. But rotating it is reasonable practice when team members change.

How do I host the verification file?

The file lives at https://yourdomain.com/<key>.txt and contains only the key string. For SvelteKit + Cloudflare Pages the simplest approach is to put the file in static/:

static/
└── 89bee770fc3a480394ff490cd251245a.txt

Content of the file is just the key:

89bee770fc3a480394ff490cd251245a

That is it. Cloudflare Pages serves the file at the root of your domain. Verify with curl:

curl https://ignax.dev/89bee770fc3a480394ff490cd251245a.txt

The response body should be the key, nothing else, with Content-Type: text/plain.

For dynamic keys (generated per environment) you can serve from a SvelteKit endpoint instead:

// src/routes/[key].txt/+server.ts
import { env } from '$env/dynamic/private';
import { error } from '@sveltejs/kit';

export const prerender = false;

export async function GET({ params }) {
  if (params.key !== env.INDEXNOW_KEY) throw error(404, 'Not found');
  return new Response(env.INDEXNOW_KEY, {
    headers: { 'content-type': 'text/plain; charset=utf-8' },
  });
}

Static file is simpler. Endpoint is more flexible for multi-environment setups.

What does the ping function look like?

The API accepts a JSON POST to api.indexnow.org/indexnow:

// src/lib/indexnow.ts
import { env } from '$env/dynamic/private';

const HOST = 'ignax.dev';

export async function pingIndexNow(urls: string[]): Promise<void> {
  if (!env.INDEXNOW_KEY) {
    console.warn('INDEXNOW_KEY not set; skipping ping');
    return;
  }
  if (urls.length === 0) return;

  const body = {
    host: HOST,
    key: env.INDEXNOW_KEY,
    keyLocation: `https://${HOST}/${env.INDEXNOW_KEY}.txt`,
    urlList: urls,
  };

  const res = await fetch('https://api.indexnow.org/indexnow', {
    method: 'POST',
    headers: { 'content-type': 'application/json; charset=utf-8' },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    console.error('IndexNow ping failed', res.status, await res.text());
  }
}

A few details worth flagging:

  1. Reads the key from environment variables, not from code.
  2. Skips silently if the key is missing (graceful degradation on local dev).
  3. Includes the keyLocation so the receiving engine can verify the file lives at your domain.
  4. Posts all URLs in one request; the API accepts up to 10,000 URLs per call.
  5. Logs failures without crashing. IndexNow is fire-and-forget; a failure should not break your build.

How do I trigger the ping on publish?

Three integration points work, depending on your workflow.

Option A: SvelteKit API endpoint, called by your CI after deploy:

// src/routes/api/indexnow/+server.ts
import { env } from '$env/dynamic/private';
import { json } from '@sveltejs/kit';
import { pingIndexNow } from '$lib/indexnow';

export const prerender = false;

export async function POST({ request }) {
  const auth = request.headers.get('authorization');
  if (auth !== `Bearer ${env.INDEXNOW_TRIGGER_SECRET}`) {
    return new Response('Unauthorized', { status: 401 });
  }

  const { urls } = await request.json();
  await pingIndexNow(urls);
  return json({ ok: true, count: urls.length });
}

CI calls it after deploy with the list of changed URLs.

Option B: GitHub Action that runs after the Cloudflare Pages deploy:

# .github/workflows/indexnow.yml
name: IndexNow Ping

on:
  push:
    branches: [main]
    paths:
      - 'src/content/**'

jobs:
  ping:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2
      - name: Compute changed URLs
        id: changed
        run: |
          urls=$(git diff --name-only HEAD~1 HEAD -- 'src/content/**.md' \
            | awk -F/ '{print "https://ignax.dev/articles/"$NF}' \
            | sed 's/\.md$//' \
            | jq -R . | jq -s .)
          echo "urls=$urls" >> $GITHUB_OUTPUT
      - name: Ping IndexNow
        run: |
          curl -X POST https://api.indexnow.org/indexnow \
            -H "Content-Type: application/json" \
            -d "{\"host\":\"ignax.dev\",\"key\":\"${{ secrets.INDEXNOW_KEY }}\",\"keyLocation\":\"https://ignax.dev/${{ secrets.INDEXNOW_KEY }}.txt\",\"urlList\":${{ steps.changed.outputs.urls }}}"

Option C: Cloudflare Pages Hook, Cloudflare offers an IndexNow integration in the dashboard. One-click; no code. The trade-off is less control over which URLs get pinged (it pings the sitemap diff, not your specific change set).

For most projects Option B (GitHub Action diffing the markdown files) is the cleanest fit. The content registry pattern from Cloudflare Pages SEO setup plays directly into it, your markdown files are the source of truth.

How do I verify it is working?

Three checks, in order:

  1. Curl the key file: curl https://yourdomain.com/<key>.txt should return the key with HTTP 200.
  2. Test the API call manually with curl:
curl -X POST https://api.indexnow.org/indexnow \
  -H "Content-Type: application/json" \
  -d '{
    "host": "ignax.dev",
    "key": "89bee770fc3a480394ff490cd251245a",
    "keyLocation": "https://ignax.dev/89bee770fc3a480394ff490cd251245a.txt",
    "urlList": ["https://ignax.dev/articles/indexnow-setup"]
  }'

A 200 OK or 202 Accepted response confirms the API accepted the ping.

  1. Check Bing Webmaster Tools: URL Inspection on the pinged URL within an hour should show "Discovered" or "Crawled" status. If it stays at "Submitted but not indexed" for more than 24 hours something is off (usually a robots.txt block or a sitemap omission).

What if the ping fails?

Common failure modes and fixes:

Error Cause Fix
403 Forbidden Key file not reachable or content mismatch Verify the txt file content matches the key exactly
422 Unprocessable Malformed JSON or URL outside the host domain Check the URL list, all URLs must match the host field
200 but no crawl Robots.txt blocks the URL Inspect robots.txt; remove Disallow for the URL pattern
Timeout Network issue or rate limit Retry with exponential backoff; cap at 10,000 URLs per call

The IndexNow protocol is forgiving, failed pings are not penalized. The cost of a botched ping is the missed indexing, not a domain demerit.

What is the AEO angle?

IndexNow indirectly improves AEO citation rates because:

  • Bing's index feeds ChatGPT browsing. Faster Bing crawl means faster ChatGPT awareness.
  • Yandex is a meaningful index in Eastern Europe and feeds some AI engines indirectly.
  • Aggregators (the IndexNow protocol partners) include several smaller engines whose data feeds different LLMs.

The chain is indirect but real: IndexNow → faster Bing crawl → ChatGPT browses Bing → your URL appears in ChatGPT citations. For the broader AEO context see how to get cited by ChatGPT and what is AEO.

What external references should I read?

IndexNow is the cheapest AEO + SEO win available in 2026. Wire it once, ping forever, watch Bing-side crawl latency drop from days to minutes. For the broader publish pipeline see Cloudflare Pages SEO setup.

Ready to wire IndexNow? Email hello@ignax.dev with your stack. Most integrations take under an hour.

Frequently asked questions

Does Google support IndexNow?

Not officially as of 2026. Google has its own indexing API and prefers webmasters use Google Search Console URL Inspection plus sitemap updates. IndexNow is supported by Bing, Yandex, Seznam, Naver, IndexNow.org aggregators, and several smaller engines. The Bing-side support is what matters for AEO since Bing's index feeds ChatGPT browsing and several other AI engines.

How fast does IndexNow actually work?

Bing typically crawls within minutes of an IndexNow ping for sites with established index presence. The first ping on a brand-new domain may take longer because the engine has to validate the key file. Yandex is slightly slower but still hours, not days. The realistic expectation: a published URL is in Bing's index within 1 to 24 hours of IndexNow ping, versus 1 to 7 days without.

Where do I store the IndexNow key?

Two places. First, as a text file at the root of your domain. yourdomain.com/<key>.txt, containing only the key as its content. Second, in an environment variable in your build pipeline so the API call can include it. The key is not sensitive (it is public via the txt file) but rotating it is reasonable practice if your team changes.

How do I integrate IndexNow with SvelteKit on Cloudflare Pages?

Two pieces. The key txt file lives in /static/<key>.txt so it ships at the domain root. The API call lives in a Cloudflare Pages Function or a build-time hook that fires on every deploy. The simplest pattern is a Wrangler trigger or a GitHub Action that posts to api.indexnow.org after the deploy completes. Total setup is about 30 minutes.

Can I ping IndexNow for URLs that were not changed?

You can but you should not. The protocol is for new or updated content. Spamming IndexNow with unchanged URLs eventually drops your trust with the receiving engines and they ignore your pings. Discipline the integration to ping only on actual content changes, new published files, updated existing files based on git diff, or manual triggers.

What about IndexNow on WordPress?

Plugins exist (Rank Math, Yoast. The official IndexNow plugin) and they work fine. The setup is two clicks: generate a key in the plugin, paste it. The plugin handles the txt file and the API call on every publish. Cloudflare also offers IndexNow as a one-click add-on for any site behind Cloudflare's proxy, which is the cleanest option if you do not want plugin clutter.