If you’re still waiting days or weeks for search engines to discover your new content, you’re missing out on one of the web’s best-kept secrets: IndexNow
IndexNow is a protocol that lets you instantly notify search engines when your content changes. Instead of waiting for crawlers to stumble upon your updates, you proactively tell them “hey, I just published something new!” The beauty of IndexNow is its simplicity: submit a URL once, and all participating search engines including Microsoft Bing, DuckDuckGo, and others get notified automatically. Think of it as a notification system for the web.
Google doesn’t support IndexNow yet, so for Google you’ll still rely on traditional XML sitemaps and their own indexing API. But for the rest of the search ecosystem, IndexNow is a game changer.
Traditional search engine crawling is inefficient. Search engines waste resources crawling unchanged pages while your new content sits undiscovered for days or weeks, and you have no control over when crawlers visit your site. With IndexNow, new content gets indexed in hours (sometimes minutes), search engines only crawl what actually changed, you control when to notify search engines, and it’s completely free and easy to implement.
The workflow is straightforward. You generate an API key which is just a random string, host a verification file at your domain root, submit URLs to IndexNow API when content changes, and IndexNow shares your submission with all partner search engines. No OAuth flows, no complicated authentication, no rate limits to worry about within reason.
Setting Up IndexNow
This guide includes implementation examples for multiple frameworks and platforms. Click on your technology below to jump directly to its implementation guide:
| Technology | Jump to Section |
|---|---|
| Astro | Build-time integration |
| React | Custom hooks |
| Next.js | API routes & Server Components |
| Vue | Composables |
| Nuxt | Server API |
| Remix | Actions & Loaders |
| SvelteKit | Server routes |
| Angular | Services & DI |
| Django | Views & JsonResponse |
| Flask | Routes & jsonify |
| FastAPI | POST endpoints & Pydantic |
| Express.js | Routes & Middleware |
| Laravel | Routes & Controllers |
| Ruby on Rails | Controllers & Routes |
| Spring Boot | RestController & PostMapping |
| ASP.NET Core | Controllers & IActionResult |
| WordPress | REST API endpoints |
| Vanilla JS | Direct API calls |
Generating Your IndexNow API Key
The first step in setting up IndexNow is generating your API key. This key proves to search engines that you own the domain and authorizes your IndexNow submissions.
Your API key should be a random hexadecimal string between 8-128 characters. The simplest way to generate one is using openssl:
openssl rand -hex 16This generates something like 19942de05a08448b2f69abd9cfa9f9b8. Save this key because you’ll need it for all IndexNow submissions.
Next, create a text file named exactly after your API key with a .txt extension in your public directory. This file proves to search engines that you own the domain:
echo "19942de05a08448b2f69abd9cfa9f9b8" > public/19942de05a08448b2f69abd9cfa9f9b8.txtThe file must be named exactly as your API key, located at your domain root so it’s accessible at https://yourdomain.com/[key].txt, and contain only the API key with no extra whitespace or characters. After deployment, verify it’s accessible:
curl https://yourdomain.com/19942de05a08448b2f69abd9cfa9f9b8.txtYou should see your API key returned with no extra formatting.
API Key Security Best Practices
The IndexNow API key sits in an unusual security position. Anyone can discover it by visiting https://yourdomain.com/[key].txt because search engines need to verify domain ownership. Yet you should still protect how your application uses this key. If someone gets hold of it and uses it in their own code, they could submit URLs on your behalf, trigger rate limiting with excessive submissions, or worse, submit malicious URLs that damage your domain’s reputation with search engines. They could also track exactly when and what you’re submitting.
Version Control
Never commit API keys to Git. Add .env to your .gitignore file and use .env.example with placeholder values for documentation. The verification file (your-key.txt) in the public directory is fine to commit since it needs to be publicly accessible anyway.
Environment Variables
Store your key as INDEXNOW_API_KEY in your .env file locally. Add it to your hosting platform’s environment variables (Vercel, Netlify, whatever you’re using). Never hardcode the key directly in your source code, even though it’s technically public. The point isn’t to hide the key itself but to control who can use it in your application.
Endpoint Protection
Add authentication to your IndexNow submission endpoints using API keys, OAuth, or JWT. Implement rate limiting to prevent abuse. Validate incoming requests and sanitize URLs before submitting them. Log all submissions so you can audit what went through and when.
Monitoring
Watch for unexpected submission patterns. Set up alerts for rate limit errors. Review your IndexNow submission logs regularly. If something looks off, rotate your API key immediately.
Authentication Example
export const POST: APIRoute = async ({ request }) => { // Check for authorization header const authHeader = request.headers.get('authorization'); const expectedToken = process.env.API_SECRET_TOKEN;
if (!authHeader || authHeader !== `Bearer ${expectedToken}`) { return new Response(JSON.stringify({ success: false, error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); }
// Your IndexNow submission logic here... const apiKey = process.env.INDEXNOW_API_KEY; // ... rest of implementation};Key Rotation
If you suspect your IndexNow API key has been compromised, generate a new one using openssl rand -hex 16. Update the verification file in your public directory. Update INDEXNOW_API_KEY in all your environments (local, staging, production). Redeploy your application. Wait 24-48 hours before deleting the old verification file so search engines have time to update their records.
Treat your IndexNow API key like a password: keep it in environment variables, never commit it to source code, protect your endpoints with authentication, and watch for suspicious activity. The key itself must be publicly accessible via the verification file, but controlling who can use it in your application prevents abuse.
Now that you have your API key set up securely, choose your framework or technology from the table above to see the specific implementation guide.
Astro Implementation
Astro’s integration system allows you to hook into the build process and automatically submit URLs to IndexNow every time you deploy. Unlike manual submissions or client-side approaches, this method ensures complete coverage of your site by reading the generated sitemap and submitting all URLs without any intervention.
The integration works by tapping into Astro’s astro:build:done hook, which fires after the build completes and all static files including your sitemap have been generated. This is the perfect timing to read your sitemap, extract all URLs, and submit them to IndexNow in a single batch operation.
This approach works well for content-heavy sites where you want guaranteed indexing of every page. Whether you’re deploying to Vercel, Netlify, or any other platform, the integration runs automatically as part of your build process, so you never have to remember to manually submit URLs.
Before implementing, make sure you’ve generated your IndexNow API key using the instructions in the Generating Your IndexNow API Key section above.
First, create a utility file to handle IndexNow submissions:
export interface IndexNowSubmission { host: string; key: string; keyLocation?: string; urlList: string[];}
export interface IndexNowResponse { success: boolean; statusCode?: number; message?: string; error?: string;}
export async function submitToIndexNow( urls: string[], apiKey: string, host: string): Promise<IndexNowResponse> { // Validate inputs if (!urls || urls.length === 0) { return { success: false, error: 'No URLs provided' }; }
if (urls.length > 10000) { return { success: false, error: 'Too many URLs. Maximum is 10,000 per request.' }; }
if (!apiKey || apiKey.length < 8 || apiKey.length > 128) { return { success: false, error: 'Invalid API key. Must be between 8-128 characters.' }; }
const payload: IndexNowSubmission = { host, key: apiKey, urlList: urls };
try { const response = await fetch('https://api.indexnow.org/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(payload) });
// Handle responses if (response.status === 200) { return { success: true, statusCode: 200, message: `Successfully submitted ${urls.length} URL(s) to IndexNow` }; } else if (response.status === 202) { return { success: true, statusCode: 202, message: `URLs received and will be processed (${urls.length} URL(s))` }; } else if (response.status === 400) { return { success: false, statusCode: 400, error: 'Bad request - Invalid format' }; } else if (response.status === 403) { return { success: false, statusCode: 403, error: 'Forbidden - Key verification failed' }; } else if (response.status === 422) { return { success: false, statusCode: 422, error: 'Unprocessable Entity - URLs not in host domain or limit exceeded' }; } else if (response.status === 429) { return { success: false, statusCode: 429, error: 'Too Many Requests - Rate limit exceeded' }; } else { return { success: false, statusCode: response.status, error: `Unexpected status code: ${response.status}` }; } } catch (error) { return { success: false, error: `Network error: ${error instanceof Error ? error.message : 'Unknown error'}` }; }}
export async function submitSingleUrl( url: string, apiKey: string, host: string): Promise<IndexNowResponse> { return submitToIndexNow([url], apiKey, host);}
export async function submitInBatches( urls: string[], apiKey: string, host: string, chunkSize: number = 10000): Promise<IndexNowResponse[]> { const results: IndexNowResponse[] = [];
for (let i = 0; i < urls.length; i += chunkSize) { const chunk = urls.slice(i, i + chunkSize); const result = await submitToIndexNow(chunk, apiKey, host); results.push(result);
// Add delay between batches to avoid rate limiting if (i + chunkSize < urls.length) { await new Promise(resolve => setTimeout(resolve, 1000)); } }
return results;}This utility provides three functions: submitToIndexNow for submitting multiple URLs, submitSingleUrl for convenience when submitting one URL, and submitInBatches for handling large URL lists with automatic chunking and rate limit protection.
Next, create an API endpoint for manual URL submissions:
import type { APIRoute } from 'astro';import { submitToIndexNow } from '../../utils/indexnow';
export const prerender = false;
export const GET: APIRoute = async () => { return new Response(JSON.stringify({ message: 'IndexNow API endpoint', apiKey: process.env.INDEXNOW_API_KEY ? 'configured' : 'not configured', usage: 'POST with JSON body: { "urls": ["https://example.com/page"] }' }), { status: 200, headers: { 'Content-Type': 'application/json' } });};
export const POST: APIRoute = async ({ request }) => { const apiKey = process.env.INDEXNOW_API_KEY;
if (!apiKey) { return new Response(JSON.stringify({ success: false, error: 'INDEXNOW_API_KEY environment variable not set' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); }
try { const body = await request.json(); const { urls } = body;
if (!urls || !Array.isArray(urls)) { return new Response(JSON.stringify({ success: false, error: 'Invalid request body. Expected { "urls": ["url1", "url2"] }' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); }
const result = await submitToIndexNow(urls, apiKey, 'yourdomain.com');
return new Response(JSON.stringify(result), { status: result.success ? 200 : 500, headers: { 'Content-Type': 'application/json' } }); } catch (error) { return new Response(JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'Unknown error' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); }};This endpoint allows you to manually submit URLs by making POST requests to /api/indexnow. The GET endpoint provides a status check showing whether your API key is configured.
Finally, create the Astro integration plugin for automatic submission:
import type { AstroIntegration } from 'astro';import { readFile } from 'fs/promises';import { join } from 'path';import { submitInBatches } from '../utils/indexnow';
interface IndexNowConfig { enabled?: boolean; verbose?: boolean; apiKey?: string;}
export function indexNowSubmit(config: IndexNowConfig = {}): AstroIntegration { const { enabled = true, verbose = true } = config;
return { name: 'indexnow-submit', hooks: { 'astro:build:done': async ({ dir }) => { if (!enabled) { if (verbose) { console.log('[indexnow-submit] Skipped (disabled)'); } return; }
const apiKey = config.apiKey || process.env.INDEXNOW_API_KEY;
if (!apiKey) { console.warn( '[indexnow-submit] Skipped: INDEXNOW_API_KEY not set' ); return; }
try { // Read sitemap const sitemapPath = join(dir.pathname, 'sitemap-0.xml'); const sitemapContent = await readFile(sitemapPath, 'utf-8');
// Extract URLs const urlMatches = sitemapContent.matchAll(/<loc>(.*?)<\/loc>/g); const urls = Array.from(urlMatches, match => match[1]);
if (urls.length === 0) { console.warn('[indexnow-submit] No URLs found in sitemap'); return; }
if (verbose) { console.log(`[indexnow-submit] Found ${urls.length} URLs`); console.log('[indexnow-submit] Submitting to IndexNow...'); }
// Submit in batches const results = await submitInBatches(urls, apiKey, 'yourdomain.com');
const successCount = results.filter(r => r.success).length; const failCount = results.filter(r => !r.success).length;
if (verbose) { console.log( `[indexnow-submit] âś“ Completed: ${successCount} successful, ${failCount} failed` ); }
if (successCount > 0) { console.log( `[indexnow-submit] âś“ Successfully submitted ${urls.length} URLs` ); } } catch (error) { console.error( `[indexnow-submit] Error: ${error instanceof Error ? error.message : 'Unknown error'}` ); } }, }, };}The integration function returns an Astro integration object with a name and hooks property. The hook function receives the build output directory, which it uses to locate the generated sitemap file. It then validates the API key exists, reads the sitemap XML, extracts all URLs using regex matching, and submits them in batches using the utility function we created earlier.
The configuration object accepts enabled and verbose flags for controlling behavior, plus an optional apiKey that falls back to environment variables. This flexibility lets you disable submissions in development or customize logging based on your deployment environment.
Now register the integration in your Astro configuration:
import { defineConfig } from 'astro/config';import { indexNowSubmit } from './src/plugins/indexNowSubmit';
export default defineConfig({ integrations: [ indexNowSubmit({ enabled: true, // Set to false to disable verbose: true // Set to false for minimal logging }) ]});With this configuration in place, every time you run npm run build or yarn build, the integration will automatically read your sitemap and submit all URLs to IndexNow. You’ll see console output showing how many URLs were found and whether the submission succeeded, giving you immediate feedback during deployment.
The integration handles errors gracefully by catching and logging them without breaking your build process. If the sitemap doesn’t exist or the API key isn’t set, it will log a warning and continue, ensuring your builds don’t fail due to IndexNow issues.
React Implementation
React applications can integrate IndexNow through custom hooks that trigger submissions when content changes. The key is to create a reusable hook that handles the API call and manages the submission state. This approach works well for client-side rendered React apps, React with server-side rendering, or Create React App setups.
The hook pattern makes it easy to trigger IndexNow submissions from anywhere in your component tree. You can call it when publishing new blog posts, updating pages, or whenever content changes that should be indexed. The hook accepts a URL and a boolean flag that determines whether to submit, giving you full control over when submissions happen.
Start by creating the custom hook:
import { useEffect } from 'react';
export function useIndexNow(url: string, shouldSubmit: boolean) { useEffect(() => { if (!shouldSubmit) return;
fetch('/api/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ urls: [url] }) }) .then(res => res.json()) .then(result => { if (result.success) { console.log('Submitted to IndexNow:', url); } }) .catch(console.error); }, [url, shouldSubmit]);}This hook uses the useEffect dependency array to automatically trigger submissions when either the URL or the shouldSubmit flag changes. The early return prevents unnecessary API calls when shouldSubmit is false.
Now use it in your components when publishing new content:
function BlogPost({ url, justPublished }) { useIndexNow(url, justPublished);
return ( <article> <h1>My Blog Post</h1> {/* your content */} </article> );}The justPublished prop would be set to true immediately after creating or updating content, triggering the IndexNow submission. You might set this based on query parameters, state management, or after a successful save operation. For example, if you’re using React Query or Redux, you could trigger the submission after a successful mutation or action.
Next.js Implementation
Next.js applications benefit from IndexNow integration at multiple levels including API routes, server actions, and the app router. The framework’s hybrid nature means you can handle IndexNow submissions both server-side and client-side depending on your needs.
For API routes in the pages directory, create an endpoint that accepts URL submissions:
import type { NextApiRequest, NextApiResponse } from 'next';import { submitToIndexNow } from '@/utils/indexnow';
export default async function handler( req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); }
const { urls } = req.body; const result = await submitToIndexNow(urls, process.env.INDEXNOW_API_KEY!, 'yourdomain.com');
res.status(result.success ? 200 : 500).json(result);}This API route handles POST requests and validates the request method before processing. It extracts URLs from the request body and passes them to the IndexNow utility function along with your API key from environment variables. The response includes the full result object so clients can handle errors appropriately.
For Next.js 13+ with the app router, you can use route handlers instead. Create a file at app/api/indexnow/route.ts with similar logic but using the new Route Handler API. Server Actions are another option where you can directly call IndexNow from server components without needing a separate API endpoint.
You can also integrate IndexNow into your build process using next.config.js. Add a plugin that reads your sitemap after build and automatically submits all URLs, similar to the Astro integration shown earlier.
Vue Implementation
Vue 3 applications work best with composables that follow the Composition API pattern. The composable approach provides reactive integration with Vue’s reactivity system, making it easy to trigger IndexNow submissions based on component state changes.
Create a composable that watches for changes and submits URLs:
import { watch } from 'vue';
export function useIndexNow(url: Ref<string>, shouldSubmit: Ref<boolean>) { watch(shouldSubmit, async (submit) => { if (!submit) return;
try { const response = await fetch('/api/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ urls: [url.value] }) });
const result = await response.json(); if (result.success) { console.log('Submitted to IndexNow:', url.value); } } catch (error) { console.error('IndexNow submission failed:', error); } });}The composable uses Vue’s watch function to observe the shouldSubmit ref. When it becomes true, the composable triggers an async fetch to your IndexNow API endpoint. This pattern integrates with Vue’s reactivity, so any state change that sets shouldSubmit to true will automatically trigger a submission.
In your Vue components, you can use the composable like this with reactive refs. Import the composable, create refs for your URL and submission flag, and the composable will handle the rest. For example, in a blog editor component, you might set shouldSubmit to true after successfully saving a post to your database.
The beauty of this approach is that it’s completely reactive and composable. You can combine it with other Vue composables like useFetch or useAsyncData to create a complete content publishing workflow that automatically notifies search engines when new content goes live.
Nuxt Implementation
Nuxt provides a server API that makes IndexNow integration straightforward. With Nuxt’s file-based routing for API endpoints, you can create server routes that handle IndexNow submissions with minimal configuration.
Create a server API endpoint using Nuxt’s conventions:
import { submitToIndexNow } from '~/utils/indexnow';
export default defineEventHandler(async (event) => { const { urls } = await readBody(event);
const result = await submitToIndexNow( urls, process.env.INDEXNOW_API_KEY!, 'yourdomain.com' );
return result;});Nuxt’s defineEventHandler wraps your endpoint logic and provides type-safe access to the request through readBody. The .post.ts suffix in the filename automatically restricts this endpoint to POST requests only, eliminating the need for manual method checking.
The endpoint imports your IndexNow utility function and environment variables are automatically available through process.env. Since this runs on the server, you have direct access to all Node.js APIs and can safely use sensitive API keys without exposing them to the client.
From the client side, you can call this endpoint using Nuxt’s $fetch utility or the standard Fetch API. The integration works with Nuxt’s server-side rendering, meaning you can trigger IndexNow submissions during SSR, static generation, or client-side navigation.
For automatic submissions during build, you can create a Nuxt module that hooks into the build process. This module would read your sitemap after generation and submit all URLs to IndexNow, ensuring every page gets indexed whenever you deploy.
Vanilla JavaScript Implementation
For websites built without frameworks or static sites that just need JavaScript, you can implement IndexNow with a simple standalone function. This approach works for any website that can make HTTP requests, whether it’s a traditional multi-page application, a static site, or even a WordPress theme.
Here’s a complete implementation that works anywhere:
async function submitToIndexNow(urls) { const apiKey = 'your-api-key-here'; const host = 'yourdomain.com';
const payload = { host: host, key: apiKey, urlList: urls };
try { const response = await fetch('https://api.indexnow.org/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(payload) });
return await response.json(); } catch (error) { console.error('IndexNow submission failed:', error); }}
// UsagesubmitToIndexNow(['https://yourdomain.com/new-page']);This vanilla implementation handles everything in a single function. It constructs the payload according to IndexNow specifications, makes the POST request directly to the IndexNow API endpoint, and handles both success and error cases.
The function is completely self-contained with no dependencies, making it perfect for adding to existing sites without any build process. You can include it in a script tag, bundle it with your existing JavaScript, or even inline it directly in your HTML.
The key difference from the framework approaches is that you’re hitting the IndexNow API directly rather than going through your own backend endpoint. This means your API key is exposed in the client code, which is fine for IndexNow since the key is already public in your verification file. However, if you want to keep submission logic server-side, you could modify this to call your own API endpoint instead, similar to the framework examples.
You can trigger this function from anywhere, like after a form submission, when content loads dynamically, or even attach it to click events on publish buttons in a content management system.
SvelteKit Implementation
SvelteKit’s server-side routing makes IndexNow integration remarkably straightforward with its file-based API routes. The framework’s convention-over-configuration approach means you can create endpoints with minimal boilerplate, and the built-in request handling gives you everything needed to process IndexNow submissions securely on the server.
Create a server route for handling IndexNow submissions:
import { json } from '@sveltejs/kit';import type { RequestHandler } from './$types';import { submitToIndexNow } from '$lib/utils/indexnow';import { INDEXNOW_API_KEY } from '$env/static/private';
export const POST: RequestHandler = async ({ request }) => { try { const { urls } = await request.json();
if (!urls || !Array.isArray(urls)) { return json( { success: false, error: 'Invalid request body' }, { status: 400 } ); }
const result = await submitToIndexNow(urls, INDEXNOW_API_KEY, 'yourdomain.com');
return json(result, { status: result.success ? 200 : 500 }); } catch (error) { return json( { success: false, error: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); }};The +server.ts naming convention tells SvelteKit this is a server-only route that won’t be included in client bundles. Environment variables use the $env/static/private module, ensuring your API key stays server-side. For client-side usage, create a Svelte store that manages submission state, and the reactive system automatically updates your UI based on submission status.
Angular Implementation
Angular’s dependency injection system and service-based architecture make it ideal for creating reusable IndexNow integration. Create a dedicated service for handling submissions, inject it wherever needed, and maintain clean, testable code throughout your application.
import { Injectable, inject } from '@angular/core';import { HttpClient, HttpHeaders } from '@angular/common/http';import { Observable, throwError } from 'rxjs';import { catchError, tap } from 'rxjs/operators';
interface IndexNowResponse { success: boolean; statusCode?: number; message?: string; error?: string;}
@Injectable({ providedIn: 'root'})export class IndexNowService { private http = inject(HttpClient); private apiUrl = '/api/indexnow';
submitUrls(urls: string[]): Observable<IndexNowResponse> { const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
return this.http.post<IndexNowResponse>( this.apiUrl, { urls }, { headers } ).pipe( tap(result => { if (result.success) { console.log('IndexNow submission successful:', result.message); } }), catchError(error => { console.error('IndexNow submission failed:', error); return throwError(() => new Error(error.message || 'Submission failed')); }) ); }
submitSingleUrl(url: string): Observable<IndexNowResponse> { return this.submitUrls([url]); }}The service uses Angular’s inject() function for dependency injection, and providedIn: 'root' makes it a singleton available throughout your application. RxJS operators like tap and catchError provide clean error handling. Inject the service in components and call its methods when publishing content, with observables handling async operations.
Laravel Implementation
Laravel’s elegant routing and controller system make IndexNow integration straightforward with its expressive syntax. The framework’s built-in HTTP client and response helpers provide everything needed to create clean, maintainable IndexNow endpoints that feel natural within the Laravel ecosystem.
Laravel’s service container and dependency injection make it easy to create reusable IndexNow services that can be injected anywhere in your application. Whether you’re building a REST API, a traditional web app, or a headless CMS, Laravel’s flexibility ensures IndexNow integration fits into your architecture.
Create a controller to handle IndexNow submissions:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;use Illuminate\Http\JsonResponse;use Illuminate\Support\Facades\Http;use Illuminate\Support\Facades\Validator;
class IndexNowController extends Controller{ public function submit(Request $request): JsonResponse { $validator = Validator::make($request->all(), [ 'urls' => 'required|array', 'urls.*' => 'required|url' ]);
if ($validator->fails()) { return response()->json([ 'success' => false, 'error' => 'Invalid request body' ], 400); }
$urls = $request->input('urls'); $apiKey = config('services.indexnow.api_key'); $host = config('app.url');
$payload = [ 'host' => parse_url($host, PHP_URL_HOST), 'key' => $apiKey, 'urlList' => $urls ];
try { $response = Http::withHeaders([ 'Content-Type' => 'application/json; charset=utf-8' ])->post('https://api.indexnow.org/indexnow', $payload);
if ($response->successful() || $response->status() === 202) { return response()->json([ 'success' => true, 'statusCode' => $response->status(), 'message' => "Successfully submitted " . count($urls) . " URL(s) to IndexNow" ]); }
return response()->json([ 'success' => false, 'statusCode' => $response->status(), 'error' => 'IndexNow submission failed' ], 500);
} catch (\Exception $e) { return response()->json([ 'success' => false, 'error' => $e->getMessage() ], 500); } }}Register the route in your routes file:
use App\Http\Controllers\IndexNowController;
Route::post('/indexnow', [IndexNowController::class, 'submit']);Add your IndexNow API key to the config. Laravel’s configuration system keeps sensitive data in environment variables:
return [ // Other services...
'indexnow' => [ 'api_key' => env('INDEXNOW_API_KEY'), ],];Laravel’s validator ensures URLs are properly formatted before submission. The built-in HTTP client handles the IndexNow API call with clean, readable syntax. Response helpers like response()->json() make it easy to return properly formatted JSON responses with appropriate status codes.
Ruby on Rails Implementation
Ruby on Rails brings convention over configuration to IndexNow integration, making it simple to create clean, RESTful endpoints. The framework’s emphasis on developer happiness means you can build IndexNow functionality with minimal boilerplate code.
Rails’ built-in HTTP client and strong parameters make handling IndexNow submissions secure and straightforward. The framework’s MVC architecture naturally separates concerns, allowing you to organize your IndexNow logic in a way that’s maintainable and testable.
Create a controller to handle IndexNow submissions:
class IndexNowController < ApplicationController skip_before_action :verify_authenticity_token
def submit unless params[:urls].is_a?(Array) render json: { success: false, error: 'Invalid request body. Expected urls array' }, status: :bad_request return end
urls = params[:urls] api_key = ENV['INDEXNOW_API_KEY'] host = request.host
payload = { host: host, key: api_key, urlList: urls }
begin response = HTTP.post('https://api.indexnow.org/indexnow', json: payload, headers: { 'Content-Type' => 'application/json; charset=utf-8' } )
if response.status.success? || response.code == 202 render json: { success: true, statusCode: response.code, message: "Successfully submitted #{urls.length} URL(s) to IndexNow" } else render json: { success: false, statusCode: response.code, error: 'IndexNow submission failed' }, status: :internal_server_error end
rescue StandardError => e render json: { success: false, error: e.message }, status: :internal_server_error end endendAdd the route to your routes file:
Rails.application.routes.draw do post '/api/indexnow', to: 'index_now#submit'endRails’ strong parameters and built-in request/response handling make the controller code clean and secure. The skip_before_action :verify_authenticity_token is necessary for API endpoints that accept JSON from external sources. For production use, you’d want to add proper authentication or rate limiting middleware.
Add the http gem to your Gemfile for making HTTP requests, or use Rails’ built-in Net::HTTP if you prefer no additional dependencies. The controller automatically handles JSON parsing from the request body and renders JSON responses with appropriate HTTP status codes.
Django Implementation
Django’s clean, pragmatic design makes IndexNow integration straightforward with its view system and JsonResponse class. The framework’s “batteries included” philosophy means you have everything needed for handling HTTP requests and JSON responses built right in.
Django views can be either function-based or class-based, but for API endpoints like IndexNow, function-based views are often simpler and more direct. The framework’s URL routing system makes it easy to map specific paths to view functions, and the JsonResponse class handles all the details of creating properly formatted JSON responses with correct content-type headers.
Before implementing this endpoint, make sure you’ve generated your IndexNow API key using openssl rand -hex 16 as described earlier in this guide. You’ll also need to create the verification file at public/YOUR_API_KEY.txt containing your key.
Create a Django view to handle IndexNow submissions:
from django.http import JsonResponsefrom django.views.decorators.csrf import csrf_exemptfrom django.views.decorators.http import require_http_methodsimport requestsimport jsonimport os
@csrf_exempt@require_http_methods(["POST"])def indexnow_submit(request): try: data = json.loads(request.body) urls = data.get('urls', [])
if not urls or not isinstance(urls, list): return JsonResponse({ 'success': False, 'error': 'Invalid request body. Expected urls array' }, status=400)
api_key = os.environ.get('INDEXNOW_API_KEY') if not api_key: return JsonResponse({ 'success': False, 'error': 'INDEXNOW_API_KEY not configured' }, status=500)
host = request.get_host()
payload = { 'host': host, 'key': api_key, 'urlList': urls }
response = requests.post( 'https://api.indexnow.org/indexnow', json=payload, headers={'Content-Type': 'application/json; charset=utf-8'} )
if response.status_code in [200, 202]: return JsonResponse({ 'success': True, 'statusCode': response.status_code, 'message': f'Successfully submitted {len(urls)} URL(s) to IndexNow' })
return JsonResponse({ 'success': False, 'statusCode': response.status_code, 'error': 'IndexNow submission failed' }, status=500)
except json.JSONDecodeError: return JsonResponse({ 'success': False, 'error': 'Invalid JSON' }, status=400) except Exception as e: return JsonResponse({ 'success': False, 'error': str(e) }, status=500)Register the URL pattern in your Django URLconf:
from django.urls import pathfrom . import views
urlpatterns = [ path('api/indexnow/', views.indexnow_submit, name='indexnow_submit'),]The @csrf_exempt decorator is necessary for API endpoints that accept JSON from external sources, as Django’s CSRF protection expects form-encoded data. The @require_http_methods decorator ensures only POST requests are accepted. Django’s request.get_host() automatically extracts the domain from the incoming request, and JsonResponse handles serialization with proper headers.
Flask Implementation
Flask’s minimalist design and flexibility make it perfect for creating lightweight IndexNow endpoints. The framework’s simplicity means you can create a working API endpoint in just a few lines of code, yet it can handle production workloads.
Flask’s routing decorator syntax is clean and intuitive, making it obvious which functions handle which URLs. The jsonify function automatically serializes Python dictionaries to JSON with the correct content-type headers, and request.get_json() handles parsing incoming JSON payloads with proper error handling.
Before implementing this endpoint, make sure you’ve generated your IndexNow API key using openssl rand -hex 16 as described earlier in this guide. You’ll also need to create the verification file at public/YOUR_API_KEY.txt containing your key.
Create a Flask route for IndexNow submissions:
from flask import Flask, request, jsonifyimport requestsimport os
app = Flask(__name__)
@app.route('/api/indexnow', methods=['POST'])def indexnow_submit(): try: data = request.get_json()
if not data: return jsonify({ 'success': False, 'error': 'Invalid JSON' }), 400
urls = data.get('urls', [])
if not urls or not isinstance(urls, list): return jsonify({ 'success': False, 'error': 'Invalid request body. Expected urls array' }), 400
api_key = os.environ.get('INDEXNOW_API_KEY') if not api_key: return jsonify({ 'success': False, 'error': 'INDEXNOW_API_KEY not configured' }), 500
host = request.host
payload = { 'host': host, 'key': api_key, 'urlList': urls }
response = requests.post( 'https://api.indexnow.org/indexnow', json=payload, headers={'Content-Type': 'application/json; charset=utf-8'} )
if response.status_code in [200, 202]: return jsonify({ 'success': True, 'statusCode': response.status_code, 'message': f'Successfully submitted {len(urls)} URL(s) to IndexNow' }), 200
return jsonify({ 'success': False, 'statusCode': response.status_code, 'error': 'IndexNow submission failed' }), 500
except Exception as e: return jsonify({ 'success': False, 'error': str(e) }), 500
if __name__ == '__main__': app.run(debug=True)Flask’s tuple return syntax allows you to return both the response body and status code in one clean line. The request.get_json() method automatically handles JSON parsing and returns None if the content-type is wrong or the JSON is malformed, making error handling straightforward. This pattern works whether you’re deploying to a traditional server, a containerized environment, or serverless platforms.
FastAPI Implementation
FastAPI is built for Python development with automatic API documentation, data validation via Pydantic, and async support out of the box. The framework’s use of Python type hints means you get automatic request validation, serialization, and interactive API docs without writing extra code.
FastAPI’s dependency injection system and Pydantic models create self-documenting, type-safe APIs that catch errors at development time rather than runtime. The framework automatically generates OpenAPI (Swagger) documentation, making your IndexNow endpoint discoverable and testable through a web interface.
Before implementing this endpoint, make sure you’ve generated your IndexNow API key using openssl rand -hex 16 as described earlier in this guide. You’ll also need to create the verification file at public/YOUR_API_KEY.txt containing your key.
Create a Pydantic model and FastAPI endpoint:
from fastapi import FastAPI, HTTPExceptionfrom pydantic import BaseModelfrom typing import Listimport httpximport os
app = FastAPI()
class IndexNowRequest(BaseModel): urls: List[str]
class IndexNowResponse(BaseModel): success: bool statusCode: int | None = None message: str | None = None error: str | None = None
@app.post("/api/indexnow", response_model=IndexNowResponse)async def indexnow_submit(request: IndexNowRequest): try: if not request.urls: raise HTTPException( status_code=400, detail="Invalid request body. Expected urls array" )
api_key = os.environ.get('INDEXNOW_API_KEY') if not api_key: raise HTTPException( status_code=500, detail="INDEXNOW_API_KEY not configured" )
host = 'yourdomain.com'
payload = { 'host': host, 'key': api_key, 'urlList': request.urls }
async with httpx.AsyncClient() as client: response = await client.post( 'https://api.indexnow.org/indexnow', json=payload, headers={'Content-Type': 'application/json; charset=utf-8'} )
if response.status_code in [200, 202]: return IndexNowResponse( success=True, statusCode=response.status_code, message=f'Successfully submitted {len(request.urls)} URL(s) to IndexNow' )
return IndexNowResponse( success=False, statusCode=response.status_code, error='IndexNow submission failed' )
except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=str(e))FastAPI’s automatic validation means if someone sends invalid data, they get a detailed error response showing exactly what went wrong. The response_model parameter ensures your response always matches the expected schema, and the async/await syntax enables high-performance concurrent request handling. Access your auto-generated API docs at /docs to test the endpoint interactively.
Express.js Implementation
Express is the de facto standard for Node.js web applications, powering countless APIs with its minimal, flexible approach. The framework’s middleware system and routing are battle-tested and understood by millions of developers, making it an excellent choice for adding IndexNow to existing Node.js applications.
Express middleware functions have access to the request and response objects, plus a next function to pass control to the next middleware. This pattern makes it easy to add logging, authentication, or data validation before your route handlers execute. The express.json() middleware automatically parses incoming JSON payloads.
Before implementing this endpoint, make sure you’ve generated your IndexNow API key using openssl rand -hex 16 as described earlier in this guide. You’ll also need to create the verification file at public/YOUR_API_KEY.txt containing your key.
Create an Express route for IndexNow:
const express = require('express');const fetch = require('node-fetch');
const app = express();app.use(express.json());
app.post('/api/indexnow', async (req, res) => { try { const { urls } = req.body;
if (!urls || !Array.isArray(urls)) { return res.status(400).json({ success: false, error: 'Invalid request body. Expected urls array' }); }
const apiKey = process.env.INDEXNOW_API_KEY; if (!apiKey) { return res.status(500).json({ success: false, error: 'INDEXNOW_API_KEY not configured' }); }
const host = req.get('host');
const payload = { host: host, key: apiKey, urlList: urls };
const response = await fetch('https://api.indexnow.org/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(payload) });
if (response.status === 200 || response.status === 202) { return res.json({ success: true, statusCode: response.status, message: `Successfully submitted ${urls.length} URL(s) to IndexNow` }); }
return res.status(500).json({ success: false, statusCode: response.status, error: 'IndexNow submission failed' });
} catch (error) { return res.status(500).json({ success: false, error: error.message }); }});
const PORT = process.env.PORT || 3000;app.listen(PORT, () => { console.log(`Server running on port ${PORT}`);});Express’s req.get('host') extracts the domain from request headers, and the async/await syntax keeps the code clean despite the asynchronous nature of HTTP requests. This pattern integrates with existing Express applications, and you can add authentication middleware or rate limiting by simply adding more middleware functions before this route handler.
Remix Implementation
Remix brings a fresh approach to React frameworks by embracing web fundamentals and progressive enhancement. Its action and loader pattern provides type-safe data flow between server and client, making it natural to handle IndexNow submissions as part of your form processing workflow.
Remix actions run only on the server, giving you secure access to environment variables and external APIs without exposing sensitive data to the client. The framework’s convention of colocating loaders, actions, and components in a single file keeps related code together, improving maintainability and developer experience.
Before implementing this endpoint, make sure you’ve generated your IndexNow API key using openssl rand -hex 16 as described earlier in this guide. You’ll also need to create the verification file at public/YOUR_API_KEY.txt containing your key.
Create a Remix action for IndexNow:
import { json, type ActionFunctionArgs } from "@remix-run/node";
export async function action({ request }: ActionFunctionArgs) { if (request.method !== "POST") { return json({ error: "Method not allowed" }, { status: 405 }); }
try { const { urls } = await request.json();
if (!urls || !Array.isArray(urls)) { return json({ success: false, error: 'Invalid request body. Expected urls array' }, { status: 400 }); }
const apiKey = process.env.INDEXNOW_API_KEY; if (!apiKey) { return json({ success: false, error: 'INDEXNOW_API_KEY not configured' }, { status: 500 }); }
const host = new URL(request.url).host;
const payload = { host: host, key: apiKey, urlList: urls };
const response = await fetch('https://api.indexnow.org/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(payload) });
if (response.status === 200 || response.status === 202) { return json({ success: true, statusCode: response.status, message: `Successfully submitted ${urls.length} URL(s) to IndexNow` }); }
return json({ success: false, statusCode: response.status, error: 'IndexNow submission failed' }, { status: 500 });
} catch (error) { return json({ success: false, error: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 }); }}Remix’s json helper function handles serialization and sets appropriate headers automatically. The framework’s file-based routing means this file at app/routes/api.indexnow.ts automatically creates an endpoint at /api/indexnow. You can call this action from any Remix form using <Form method="post" action="/api/indexnow"> or from client-side code using fetch.
Spring Boot Implementation
Spring Boot brings enterprise-grade Java development to web applications with dependency injection, comprehensive documentation, and a massive ecosystem. The framework’s annotation-based configuration and auto-configuration features mean you can build production-ready REST APIs with minimal boilerplate code.
Spring’s RestController annotation combines Controller and ResponseBody, automatically serializing return values to JSON. The framework’s exception handling, request validation, and extensive integration options make it a solid choice for applications requiring high reliability and scalability.
Before implementing this endpoint, make sure you’ve generated your IndexNow API key using openssl rand -hex 16 as described earlier in this guide. You’ll also need to create the verification file at public/YOUR_API_KEY.txt containing your key.
Create a Spring Boot REST controller:
package com.example.controller;
import org.springframework.beans.factory.annotation.Value;import org.springframework.http.*;import org.springframework.web.bind.annotation.*;import org.springframework.web.client.RestTemplate;import java.util.HashMap;import java.util.List;import java.util.Map;
@RestController@RequestMapping("/api")public class IndexNowController {
@Value("${indexnow.api.key}") private String apiKey;
private final RestTemplate restTemplate;
public IndexNowController(RestTemplate restTemplate) { this.restTemplate = restTemplate; }
@PostMapping("/indexnow") public ResponseEntity<Map<String, Object>> submitToIndexNow( @RequestBody Map<String, Object> request) {
Map<String, Object> response = new HashMap<>();
try { @SuppressWarnings("unchecked") List<String> urls = (List<String>) request.get("urls");
if (urls == null || urls.isEmpty()) { response.put("success", false); response.put("error", "Invalid request body. Expected urls array"); return ResponseEntity.badRequest().body(response); }
if (apiKey == null || apiKey.isEmpty()) { response.put("success", false); response.put("error", "INDEXNOW_API_KEY not configured"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); }
Map<String, Object> payload = new HashMap<>(); payload.put("host", "yourdomain.com"); payload.put("key", apiKey); payload.put("urlList", urls);
HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(payload, headers);
ResponseEntity<String> indexNowResponse = restTemplate.postForEntity( "https://api.indexnow.org/indexnow", entity, String.class );
int statusCode = indexNowResponse.getStatusCodeValue();
if (statusCode == 200 || statusCode == 202) { response.put("success", true); response.put("statusCode", statusCode); response.put("message", "Successfully submitted " + urls.size() + " URL(s) to IndexNow"); return ResponseEntity.ok(response); }
response.put("success", false); response.put("statusCode", statusCode); response.put("error", "IndexNow submission failed"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
} catch (Exception e) { response.put("success", false); response.put("error", e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); } }}Configure the RestTemplate bean and add your API key to application properties:
package com.example.config;
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.client.RestTemplate;
@Configurationpublic class AppConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); }}indexnow.api.key=${INDEXNOW_API_KEY}Spring Boot’s @Value annotation injects the API key from application properties, which can reference environment variables. The RestTemplate handles HTTP communication, and ResponseEntity provides fine-grained control over response status codes and headers. This pattern integrates naturally with Spring Security for authentication and Spring’s extensive monitoring and observability features.
ASP.NET Core Implementation
ASP.NET Core brings cross-platform development to the .NET ecosystem with excellent performance and a rich set of built-in features. The framework’s controller-based architecture, dependency injection, and comprehensive middleware pipeline make it ideal for building scalable APIs.
Controllers in ASP.NET Core return IActionResult or Task<IActionResult>, allowing you to return different response types based on the operation’s result. The framework’s model binding automatically deserializes JSON request bodies into C# objects, and returning objects from controller actions automatically serializes them to JSON.
Before implementing this endpoint, make sure you’ve generated your IndexNow API key using openssl rand -hex 16 as described earlier in this guide. You’ll also need to create the verification file at public/YOUR_API_KEY.txt containing your key.
Create an ASP.NET Core controller:
using Microsoft.AspNetCore.Mvc;using System.Text;using System.Text.Json;
namespace YourApp.Controllers{ [ApiController] [Route("api/[controller]")] public class IndexNowController : ControllerBase { private readonly IConfiguration _configuration; private readonly IHttpClientFactory _httpClientFactory;
public IndexNowController( IConfiguration configuration, IHttpClientFactory httpClientFactory) { _configuration = configuration; _httpClientFactory = httpClientFactory; }
[HttpPost] public async Task<IActionResult> Submit([FromBody] IndexNowRequest request) { try { if (request.Urls == null || request.Urls.Count == 0) { return BadRequest(new { success = false, error = "Invalid request body. Expected urls array" }); }
var apiKey = _configuration["IndexNow:ApiKey"]; if (string.IsNullOrEmpty(apiKey)) { return StatusCode(500, new { success = false, error = "INDEXNOW_API_KEY not configured" }); }
var payload = new { host = "yourdomain.com", key = apiKey, urlList = request.Urls };
var client = _httpClientFactory.CreateClient(); var content = new StringContent( JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json" );
var response = await client.PostAsync( "https://api.indexnow.org/indexnow", content );
var statusCode = (int)response.StatusCode;
if (statusCode == 200 || statusCode == 202) { return Ok(new { success = true, statusCode = statusCode, message = $"Successfully submitted {request.Urls.Count} URL(s) to IndexNow" }); }
return StatusCode(500, new { success = false, statusCode = statusCode, error = "IndexNow submission failed" }); } catch (Exception ex) { return StatusCode(500, new { success = false, error = ex.Message }); } } }
public class IndexNowRequest { public List<string> Urls { get; set; } = new(); }}Configure services in your Program.cs or Startup.cs:
builder.Services.AddControllers();builder.Services.AddHttpClient();
// Add to appsettings.json:// "IndexNow": { "ApiKey": "your-api-key-here" }ASP.NET Core’s IHttpClientFactory manages HttpClient instances efficiently, preventing socket exhaustion. The framework’s built-in dependency injection makes testing straightforward by allowing easy mocking of dependencies. This controller pattern works with ASP.NET Core’s authentication, authorization, and comprehensive middleware ecosystem.
WordPress Implementation
WordPress powers over 40% of the web, and adding IndexNow to WordPress sites ensures your content reaches search engines quickly. WordPress’s REST API system provides a structured way to create custom endpoints, and the platform’s hook system makes it easy to trigger IndexNow submissions when content changes.
WordPress REST API endpoints follow RESTful conventions with proper authentication and permission checking. The platform’s extensive plugin ecosystem means you can package your IndexNow integration as a reusable plugin, or add it directly to your theme’s functions.php file for site-specific implementations.
Before implementing this endpoint, make sure you’ve generated your IndexNow API key using openssl rand -hex 16 as described earlier in this guide. You’ll also need to create the verification file at public/YOUR_API_KEY.txt containing your key, and store the key in a WordPress constant or option.
Create a WordPress REST API endpoint for IndexNow:
<?php
add_action('rest_api_init', function() { register_rest_route('indexnow/v1', '/submit', array( 'methods' => 'POST', 'callback' => 'handle_indexnow_submission', 'permission_callback' => function() { return current_user_can('edit_posts'); } ));});
function handle_indexnow_submission($request) { $params = $request->get_json_params();
if (!isset($params['urls']) || !is_array($params['urls'])) { return new WP_REST_Response(array( 'success' => false, 'error' => 'Invalid request body. Expected urls array' ), 400); }
$urls = $params['urls']; $api_key = defined('INDEXNOW_API_KEY') ? INDEXNOW_API_KEY : '';
if (empty($api_key)) { return new WP_REST_Response(array( 'success' => false, 'error' => 'INDEXNOW_API_KEY not configured' ), 500); }
$host = parse_url(get_site_url(), PHP_URL_HOST);
$payload = array( 'host' => $host, 'key' => $api_key, 'urlList' => $urls );
$response = wp_remote_post('https://api.indexnow.org/indexnow', array( 'headers' => array('Content-Type' => 'application/json; charset=utf-8'), 'body' => json_encode($payload), 'timeout' => 15 ));
if (is_wp_error($response)) { return new WP_REST_Response(array( 'success' => false, 'error' => $response->get_error_message() ), 500); }
$status_code = wp_remote_retrieve_response_code($response);
if ($status_code === 200 || $status_code === 202) { return new WP_REST_Response(array( 'success' => true, 'statusCode' => $status_code, 'message' => 'Successfully submitted ' . count($urls) . ' URL(s) to IndexNow' ), 200); }
return new WP_REST_Response(array( 'success' => false, 'statusCode' => $status_code, 'error' => 'IndexNow submission failed' ), 500);}
// Auto-submit when publishing postsadd_action('publish_post', 'auto_submit_to_indexnow', 10, 2);
function auto_submit_to_indexnow($post_id, $post) { $url = get_permalink($post_id);
$request = new WP_REST_Request('POST', '/indexnow/v1/submit'); $request->set_header('content-type', 'application/json'); $request->set_body(json_encode(array('urls' => array($url))));
rest_do_request($request);}Define your API key in wp-config.php:
define('INDEXNOW_API_KEY', 'your-api-key-here');WordPress’s wp_remote_post function handles HTTP requests with proper error checking and timeout handling. The permission_callback ensures only authorized users can trigger submissions. The publish_post action hook automatically submits URLs when content is published, providing seamless integration with WordPress’s content workflow.
Testing and Best Practices
To test your setup, first verify the key file is accessible by running curl https://yourdomain.com/19942de05a08448b2f69abd9cfa9f9b8.txt which should return your API key. Then test the API endpoint with curl https://yourdomain.com/api/indexnow to check configuration, and submit a test URL with a POST request containing a JSON body with your URLs array.
When you run your build command, look for output like [indexnow-submit] Found 50 URLs in sitemap followed by [indexnow-submit] âś“ Successfully submitted 50 URLs to IndexNow to confirm everything is working.
Understanding response codes is important. A 200 status means URLs were successfully submitted and will be indexed. A 202 means URLs were received and queued for processing. A 400 indicates a bad request so check your JSON format. A 403 means key verification failed so check your key file is accessible. A 422 means URLs aren’t in your domain or you exceeded the limit. A 429 means you hit the rate limit and need to slow down.
The most important best practice is to only submit when content actually changes. Don’t spam IndexNow with submissions. Submit URLs when you publish new content, when you update existing content significantly, or even when you delete content because search engines need to know about removals too.
Instead of submitting one URL at a time, always batch your submissions:
const urls = [ 'https://yourdomain.com/blog/post-1', 'https://yourdomain.com/blog/post-2', 'https://yourdomain.com/blog/post-3'];
await submitToIndexNow(urls, apiKey, 'yourdomain.com');Always check the response and log failures so you can debug issues. If a submission fails, you might want to retry later or alert your monitoring system.
Remember that IndexNow is a supplement, not a replacement for traditional SEO fundamentals like XML sitemaps, robots.txt, good internal linking structure, and quality content. It works best as part of a comprehensive SEO strategy.
IndexNow doesn’t provide direct feedback on indexing status, so to monitor effectiveness you’ll want to check Bing Webmaster Tools for crawl stats, monitor organic search traffic changes, use site:yourdomain.com searches to verify indexed pages, and track time-to-indexing for new content.
The most common issue you might encounter is a 403 Forbidden error, which means search engines can’t verify your key file. Make sure the file is at your domain root and not in a subdirectory, contains only the key with no extra whitespace, is publicly accessible and not behind authentication, and give it a few minutes after deployment for DNS propagation to complete.
If you get a 422 Unprocessable Entity error, it means your URLs don’t match your host or exceed limits. Ensure all URLs start with your domain, don’t include URLs from other domains, stay under 10,000 URLs per request, and use the correct protocol.
If you’ve submitted URLs but they’re not getting indexed, remember that IndexNow doesn’t guarantee indexing, just notification. Search engines still evaluate content quality before deciding to index. New domains may take longer to build trust, and some content types naturally index faster than others.
IndexNow is completely free with no API keys to purchase and no strict rate limits to worry about. Just keep in mind the 10,000 URLs per request limit, maintain reasonable request frequency without spamming, and ensure all URLs are from the same host.