Traditional search engine crawling works on the search engine's schedule, not yours. You publish a new page, submit your sitemap, and wait. Googlebot might visit in hours. Bingbot might take days. Smaller engines could take weeks. During this gap, your content is invisible to search traffic.
IndexNow inverts this model. Instead of waiting for crawlers to discover your content, you tell them it exists the moment you publish it. The protocol was created by Microsoft and Yandex in 2021 and has since been adopted by Seznam.cz and Naver. When you submit a URL to any one IndexNow endpoint, all participating search engines are notified simultaneously.
This guide covers everything you need to implement IndexNow: how the protocol works, which search engines support it, generating and hosting your verification key, building a Next.js API route for single and bulk URL submission, triggering IndexNow automatically on content publish, integrating with Vercel deployments, and monitoring submission success. Use the AgentReady scanner to check whether your site has IndexNow configured.
What is IndexNow?
IndexNow is an open protocol that allows website owners to notify search engines about URL changes instantly. It is a simple HTTP GET or POST request that says: "This URL has been created, updated, or deleted. Please re-crawl it."
The protocol requires two things from your end:
- A verification key hosted as a text file at your domain root, proving you own the site.
- An API call to the IndexNow endpoint with the URL and your key.
That is it. No OAuth, no API keys to manage, no rate limit tokens. The protocol is deliberately simple.
Which search engines support IndexNow?
| Search Engine | IndexNow Support | Endpoint | Market Share |
|---|---|---|---|
| Bing | Yes (co-creator) | api.indexnow.org |
~3% global, ~10% US desktop |
| Yandex | Yes (co-creator) | yandex.com/indexnow |
~1% global, ~60% Russia |
| Seznam.cz | Yes | seznam.cz/indexnow |
~25% Czech Republic |
| Naver | Yes | searchadvisor.naver.com/indexnow |
~55% South Korea |
| No | N/A | ~90% global |
The key benefit of IndexNow is that submitting to any one endpoint notifies all participating engines. You only need to call api.indexnow.org once, and Bing, Yandex, Seznam, and Naver all receive the notification.
Why Bing matters more than you think
Many developers dismiss Bing as irrelevant because Google dominates global search. But Bing powers several important surfaces:
- Microsoft Copilot uses Bing's index for grounding and real-time search.
- ChatGPT with browsing historically used Bing for web search (via ChatGPT-User).
- DuckDuckGo sources its web results primarily from Bing's index.
- Yahoo Search is powered by Bing.
- Ecosia, Qwant, and other privacy-focused search engines use Bing as their backend.
By indexing quickly on Bing via IndexNow, your content becomes available across all of these surfaces, including AI-powered ones.
Generating and hosting your verification key
The verification key proves to IndexNow that you own the domain you are submitting URLs for. It is a simple string that you host as a text file at your domain root.
Step 1: Generate a key
The key can be any string of 8-128 characters containing only a-z, A-Z, 0-9, and -. A 32-character hex string is the common convention:
# Generate a random 32-character hex key
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# Example output: a969caaa7704f6a6b5532c5a89159e6a
Step 2: Host the key file
Create a text file at public/<your-key>.txt containing just the key value:
# For a Next.js project
echo "a969caaa7704f6a6b5532c5a89159e6a" > public/a969caaa7704f6a6b5532c5a89159e6a.txt
After deployment, the file must be accessible at https://yourdomain.com/a969caaa7704f6a6b5532c5a89159e6a.txt. The IndexNow endpoint validates ownership by fetching this file.
Step 3: Store the key as an environment variable
Add the key to your environment variables so your API route can reference it without hardcoding:
# .env.local
INDEXNOW_KEY=a969caaa7704f6a6b5532c5a89159e6a
Add INDEXNOW_KEY to your Vercel environment variables for production.
Next.js API route implementation
Create an API route that accepts URLs and submits them to the IndexNow endpoint. This route serves as an internal endpoint that your content management workflows can call.
Single URL submission
// app/api/indexnow/route.ts
import { NextRequest, NextResponse } from "next/server";
const INDEXNOW_KEY = process.env.INDEXNOW_KEY || "";
const SITE_HOST = "yourdomain.com";
export async function POST(req: NextRequest) {
try {
const { url, urls } = await req.json();
// Validate input
if (!url && (!urls || !Array.isArray(urls) || urls.length === 0)) {
return NextResponse.json(
{ error: "Provide a 'url' string or 'urls' array" },
{ status: 400 }
);
}
if (!INDEXNOW_KEY) {
return NextResponse.json(
{ error: "INDEXNOW_KEY not configured" },
{ status: 500 }
);
}
// Single URL submission via GET
if (url && !urls) {
const indexNowUrl = new URL("https://api.indexnow.org/indexnow");
indexNowUrl.searchParams.set("url", url);
indexNowUrl.searchParams.set("key", INDEXNOW_KEY);
const response = await fetch(indexNowUrl.toString());
return NextResponse.json({
success: response.ok,
status: response.status,
submitted: url,
});
}
// Bulk URL submission via POST
const urlList = urls || [url];
const response = await fetch("https://api.indexnow.org/indexnow", {
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({
host: SITE_HOST,
key: INDEXNOW_KEY,
keyLocation: `https://${SITE_HOST}/${INDEXNOW_KEY}.txt`,
urlList: urlList,
}),
});
return NextResponse.json({
success: response.ok,
status: response.status,
submitted: urlList.length,
urls: urlList,
});
} catch (error) {
return NextResponse.json(
{ error: "IndexNow submission failed" },
{ status: 500 }
);
}
}
Understanding the IndexNow response codes
| Status Code | Meaning |
|---|---|
200 |
URL submitted successfully |
202 |
URL accepted, will be processed later |
400 |
Invalid request (bad URL format, missing key) |
403 |
Key validation failed (key file not found or does not match) |
422 |
URL does not belong to the host |
429 |
Too many requests (rate limited) |
Both 200 and 202 mean success. The search engine will crawl the submitted URL. A 202 means it has been queued for processing rather than processed immediately.
Bulk URL submission
IndexNow supports submitting up to 10,000 URLs in a single POST request. This is ideal for batch operations like publishing multiple pages, running a site-wide content update, or re-indexing after a redesign.
Bulk submission format
// POST to https://api.indexnow.org/indexnow
{
"host": "yourdomain.com",
"key": "a969caaa7704f6a6b5532c5a89159e6a",
"keyLocation": "https://yourdomain.com/a969caaa7704f6a6b5532c5a89159e6a.txt",
"urlList": [
"https://yourdomain.com/guides/new-guide.html",
"https://yourdomain.com/blog/updated-post",
"https://yourdomain.com/products/new-product",
"https://yourdomain.com/about"
]
}
Batch submission utility
For programmatic bulk submission, create a utility function you can call from anywhere in your application:
// lib/indexnow.ts
const INDEXNOW_KEY = process.env.INDEXNOW_KEY || "";
const SITE_HOST = process.env.NEXT_PUBLIC_SITE_URL
? new URL(process.env.NEXT_PUBLIC_SITE_URL).host
: "yourdomain.com";
export async function submitToIndexNow(urls: string[]): Promise<{
success: boolean;
status: number;
}> {
if (!INDEXNOW_KEY || urls.length === 0) {
return { success: false, status: 0 };
}
try {
const response = await fetch("https://api.indexnow.org/indexnow", {
method: "POST",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
body: JSON.stringify({
host: SITE_HOST,
key: INDEXNOW_KEY,
keyLocation: `https://${SITE_HOST}/${INDEXNOW_KEY}.txt`,
urlList: urls,
}),
});
return {
success: response.ok || response.status === 202,
status: response.status,
};
} catch {
return { success: false, status: 0 };
}
}
// Single URL helper
export async function notifyIndexNow(url: string) {
return submitToIndexNow([url]);
}
Triggering IndexNow on content publish
The real power of IndexNow comes from automated integration with your content publishing workflow. Here are patterns for different scenarios.
Pattern 1: After saving content in a CMS API route
// app/api/content/publish/route.ts
import { NextRequest, NextResponse } from "next/server";
import { notifyIndexNow } from "@/lib/indexnow";
export async function POST(req: NextRequest) {
const { title, slug, content } = await req.json();
// Save content to database
// await supabase.from("posts").insert({ title, slug, content });
// Notify IndexNow (fire-and-forget, do not block on this)
const pageUrl = `https://yourdomain.com/blog/${slug}`;
notifyIndexNow(pageUrl).catch(() => {
// Silent fail - IndexNow is best-effort
});
return NextResponse.json({ success: true, url: pageUrl });
}
Pattern 2: Vercel deployment webhook
Trigger IndexNow for all pages after a successful Vercel deployment. Create a webhook endpoint that Vercel calls post-deployment:
// app/api/deploy-hook/route.ts
import { NextRequest, NextResponse } from "next/server";
import { submitToIndexNow } from "@/lib/indexnow";
// List of important URLs to re-index on every deployment
const IMPORTANT_URLS = [
"https://yourdomain.com/",
"https://yourdomain.com/services",
"https://yourdomain.com/about",
"https://yourdomain.com/contact",
"https://yourdomain.com/guides/",
// Add your key pages here
];
export async function POST(req: NextRequest) {
// Verify this is from Vercel (optional: check a secret header)
const secret = req.headers.get("x-webhook-secret");
if (secret !== process.env.DEPLOY_WEBHOOK_SECRET) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const result = await submitToIndexNow(IMPORTANT_URLS);
return NextResponse.json({
success: result.success,
submitted: IMPORTANT_URLS.length,
});
}
Pattern 3: Sitemap-based bulk submission
Parse your sitemap and submit all URLs. This is useful for initial setup or periodic re-submission:
// scripts/submit-sitemap-to-indexnow.ts
import { submitToIndexNow } from "@/lib/indexnow";
async function submitSitemap() {
// Fetch and parse your sitemap
const res = await fetch("https://yourdomain.com/sitemap.xml");
const xml = await res.text();
// Extract URLs (simple regex approach)
const urls: string[] = [];
const regex = /<loc>(.*?)<\/loc>/g;
let match;
while ((match = regex.exec(xml)) !== null) {
urls.push(match[1]);
}
// Submit in batches of 10,000
for (let i = 0; i < urls.length; i += 10000) {
const batch = urls.slice(i, i + 10000);
const result = await submitToIndexNow(batch);
console.log(
`Batch ${Math.floor(i / 10000) + 1}: ${result.status} ` +
`(${batch.length} URLs)`
);
}
}
submitSitemap();
Testing your IndexNow implementation
Verify the key file is accessible
# Check that your key file is served correctly
curl https://yourdomain.com/a969caaa7704f6a6b5532c5a89159e6a.txt
# Should output just the key:
# a969caaa7704f6a6b5532c5a89159e6a
Submit a test URL
# Single URL submission via GET
curl "https://api.indexnow.org/indexnow?url=https://yourdomain.com/test-page&key=a969caaa7704f6a6b5532c5a89159e6a"
# Should return 200 or 202
Test your API route
# Submit via your API route
curl -X POST https://yourdomain.com/api/indexnow \
-H "Content-Type: application/json" \
-d '{"url": "https://yourdomain.com/guides/indexnow-guide.html"}'
# Bulk submission
curl -X POST https://yourdomain.com/api/indexnow \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://yourdomain.com/page-1",
"https://yourdomain.com/page-2",
"https://yourdomain.com/page-3"
]
}'
Monitoring IndexNow submissions
Bing Webmaster Tools
Bing Webmaster Tools provides an IndexNow status dashboard that shows submitted URLs, processing status, and any errors. Verify your site in Bing Webmaster Tools at bing.com/webmasters and navigate to the IndexNow section to see submission history.
Logging submissions in your database
For your own analytics, log IndexNow submissions to a Supabase table:
-- Supabase migration
CREATE TABLE indexnow_submissions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
url text NOT NULL,
status_code integer,
success boolean DEFAULT false,
batch_id text,
created_at timestamptz DEFAULT now()
);
CREATE INDEX idx_indexnow_created_at
ON indexnow_submissions (created_at DESC);
Update your utility function to log submissions:
// In lib/indexnow.ts, after the fetch call:
// await supabase.from("indexnow_submissions").insert(
// urls.map(url => ({
// url,
// status_code: response.status,
// success: response.ok || response.status === 202,
// batch_id: batchId,
// }))
// );
IndexNow and the broader discovery layer
IndexNow is one piece of a comprehensive discovery strategy. It works alongside other discovery mechanisms to ensure your content reaches both traditional search engines and AI systems.
- robots.txt controls which crawlers can access your site.
- XML Sitemaps provide a complete URL inventory for crawlers to discover.
- IndexNow actively pushes notifications when content changes.
- llms.txt provides AI-specific content discovery.
- Bot tracking helps you understand which crawlers are visiting and what they access.
Together, these mechanisms ensure maximum visibility across search engines and AI platforms.
Best practices
Only submit URLs that have actually changed
Do not submit your entire sitemap on every deployment. Only submit URLs where the content has actually been created or modified. Submitting unchanged URLs wastes quota and can reduce the priority search engines assign to your future submissions.
Submit immediately after publishing
Call IndexNow as part of your publish workflow, not as a batch job that runs hours later. The sooner you notify, the sooner the content appears in search results.
Make IndexNow fire-and-forget
Never let IndexNow failures block your content publishing. Use .catch(() => {}) on the promise and move on. IndexNow is a speed optimisation, not a critical dependency. Search engines will discover your content through normal crawling even if IndexNow fails.
Include the keyLocation parameter
While technically optional, the keyLocation parameter tells search engines exactly where to find your key file, avoiding any ambiguity and speeding up verification.
Common issues and solutions
403 error: key validation failed
Cause: The key file at https://yourdomain.com/<key>.txt is not accessible or does not contain the correct key value.
Fix: Verify the file exists at the correct URL, contains only the key (no extra whitespace or newlines), and is served with a text/plain content type.
422 error: URL does not belong to host
Cause: The URL you submitted does not match the host associated with the key.
Fix: Make sure the URL domain matches the domain where your key file is hosted. You cannot use one key to submit URLs for a different domain.
Key file not served on Vercel
Cause: Next.js or Vercel's routing is interfering with the .txt file.
Fix: Place the file in the public/ directory. Vercel serves files from public/ as static assets. Verify the file is included in your deployment by checking https://yourdomain.com/<key>.txt after deploying.
Frequently Asked Questions
What is IndexNow and which search engines support it?
IndexNow is a protocol that lets website owners instantly notify search engines when content is created, updated, or deleted. It is supported by Bing, Yandex, Seznam.cz, and Naver. When you ping one IndexNow endpoint, all participating search engines are notified. Google does not support IndexNow but has its own Indexing API for specific structured data types.
Does Google support IndexNow?
No. Google does not support the IndexNow protocol. Google relies on its own crawling infrastructure, Sitemaps, and the Google Indexing API (which is limited to JobPosting and BroadcastEvent structured data). However, submitting to IndexNow still provides significant value through Bing, Yandex, Seznam, and Naver, as well as all services powered by Bing's index including Microsoft Copilot and DuckDuckGo.
How do I generate an IndexNow verification key?
Generate a unique key string of 8 to 128 characters containing letters, numbers, and hyphens. A 32-character hex string is the common convention. Use node -e "console.log(require('crypto').randomBytes(16).toString('hex'))" to generate one. Save the key as a text file at public/<key>.txt and reference it in your API calls.
Can I submit multiple URLs at once with IndexNow?
Yes. IndexNow supports bulk submission via a POST request to the /indexnow endpoint with a JSON body containing a urlList array. You can submit up to 10,000 URLs per request. This is ideal for batch operations like publishing multiple pages or re-indexing after a site-wide content update.
How do I trigger IndexNow automatically when content is published?
Call your IndexNow API route from your content publishing workflow. In a CMS, add a webhook that fires on publish. In a Next.js app, call the IndexNow endpoint from your content creation API route after successfully saving the new content. Use fire-and-forget pattern so IndexNow does not block the publishing flow.
What happens if IndexNow submission fails?
IndexNow submission is best-effort. If it fails, the search engines will still discover your content through normal crawling and sitemap processing. Failed submissions do not harm your SEO. Implement retry logic with exponential backoff for reliability, but never let IndexNow failures block your content publishing flow.
How quickly does Bing index pages after IndexNow notification?
Bing typically processes IndexNow notifications within minutes to hours. New content can appear in Bing search results within 10 minutes of submission in ideal cases. This is dramatically faster than waiting for Bing's crawler to discover the page organically, which can take days or weeks.
Is IndexNow free to use?
Yes. IndexNow is completely free and open. There are no API keys to purchase, no rate limits to worry about for normal usage, and no cost per submission. The protocol was created by Microsoft and Yandex and is designed to be universally accessible to all website owners.
By Paul Gosnell