How to Integrate IndexNow API with Node.js — Step-by-Step Guide for Instant SEO Indexing
TL;DR
- Integrate IndexNow in Node.js to speed up SEO indexing:
- Generate a 32-character API key.
- Host it as a .txt file on your domain.
- Submit URLs via a POST request to the IndexNow API.
- Verify using Bing Webmaster Tools.
- Fast, simple, and improves search engine visibility
What is IndexNow?
IndexNow is this cool open protocol that lets websites instantly tell search engines (like Bing, Yandex, and others) when you add, update, or delete stuff. It's a pretty neat way to help search engines keep their indexes current. (In-Depth Guide to How Google Search Works)
Prerequisites
You'll need a Node.js project, and make sure you've got
axiosinstalled.You gotta have access to your website’s root directory, 'cause that's where we'll put the key.
And of course, a public-facing domain (like
https://www.example.com).
Step 1: Generate Your IndexNow API Key
So, you need an api key to prove your domain is yours.
- Go ahead and generate a key. The official docs say it should be a UTF-8 key, and while they don't specify a length, a common practice is to use a 32-character key. (Generate Random UTF8) You can use an online generator, or if you're feeling code-y, use this Node.js snippet:
const crypto = require('crypto');
const key = crypto.randomBytes(16).toString('hex');
console.log("Generated IndexNow Key:", key);
Example Key:4262631fe57245bd9bd1cef01d1c3fa4
Step 2: Host the API Key on Your Site
Now, you gotta create a .txt file. Name it using your key and stick it in the root of your website.
Filename:
4262631fe57245bd9bd1cef01d1c3fa4.txtContent inside file: Just the key itself (
4262631fe57245bd9bd1cef01d1c3fa4)URL:
https://www.example.com/4262631fe57245bd9bd1cef01d1c3fa4.txt
Quick Note: If you decide to put the key file somewhere else, that's fine, but you'll need to tell IndexNow where it is using the keyLocation parameter when you submit URLs.
Step 3: Submit URLs via Node.js Script
File Structure
Here's a simple way to organize your project:
├── config.js
├── indexnow.js <-- This is where the IndexNow magic happens
└── submit.js <-- This is for running and testing the submission
config.js
Let's set up some basic config:
module.exports = {
indexNow: {
agent: 'my-custom-agent',
source: 'my-website-source'
}
};
indexnow.js — Core Logic
This is the main part of the script:
const crypto = require('crypto');
const axios = require('axios');
const config = require('./config');
function getDomainFromUrl(url) {
try {
const parsedUrl = new URL(url);
return parsedUrl.hostname;
} catch (e) {
console.error('Invalid URL:', url);
return null;
}
}
async function submitToIndexNow(siteUrl, indexNowKey, urlList) {
try {
// Basic checks to make sure we have everything
if (!siteUrl || !indexNowKey || !urlList || !Array.isArray(urlList)) {
throw new Error('Missing required parameters');
}
<span class="hljs-comment">// If there are no URLs, we can just skip it</span>
<span class="hljs-keyword">if</span> (urlList.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>) {
<span class="hljs-keyword">return</span> { <span class="hljs-attr">status</span>: <span class="hljs-string">'skipped'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'No URLs to submit'</span> };
}
<span class="hljs-comment">// Make sure the URLs are absolute</span>
urlList = urlList.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">url</span>) =></span> siteUrl.<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/\/$/</span>, <span class="hljs-string">''</span>) + <span class="hljs-string">"/"</span> + url);
<span class="hljs-keyword">const</span> siteDomain = <span class="hljs-title function_">getDomainFromUrl</span>(siteUrl);
<span class="hljs-keyword">if</span> (!siteDomain) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'Invalid site URL'</span>);
<span class="hljs-keyword">const</span> data = {
<span class="hljs-attr">host</span>: siteDomain,
<span class="hljs-attr">key</span>: indexNowKey,
<span class="hljs-comment">// Construct the keyLocation assuming it's in the root</span>
<span class="hljs-attr">keyLocation</span>: <span class="hljs-string">`<span class="hljs-subst">${siteUrl.replace(/\/$/, <span class="hljs-string">''</span>)}</span>/<span class="hljs-subst">${indexNowKey}</span>.txt`</span>,
<span class="hljs-attr">urlList</span>: urlList
};
<span class="hljs-keyword">const</span> headers = {
<span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
<span class="hljs-comment">// Creating a user-agent, just for good measure</span>
<span class="hljs-string">'User-Agent'</span>: <span class="hljs-string">`<span class="hljs-subst">${config.indexNow.agent}</span>/<span class="hljs-subst">${crypto.createHash(<span class="hljs-string">'md5'</span>).update(siteUrl).digest(<span class="hljs-string">'hex'</span>)}</span>`</span>,
<span class="hljs-string">'X-Source-Info'</span>: <span class="hljs-string">`https://<span class="hljs-subst">${config.indexNow.source}</span>/1.0/`</span>
};
<span class="hljs-comment">// Sending the request to the IndexNow API</span>
<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.<span class="hljs-title function_">post</span>(<span class="hljs-string">'https://api.indexnow.org/indexnow'</span>, data, { headers });
<span class="hljs-keyword">return</span> {
<span class="hljs-attr">status</span>: <span class="hljs-string">'success'</span>,
<span class="hljs-attr">statusCode</span>: response.<span class="hljs-property">status</span>,
<span class="hljs-attr">message</span>: <span class="hljs-string">`Successfully submitted <span class="hljs-subst">${urlList.length}</span> URLs`</span>
};
} catch (error) {
// Handling potential errors during the submission
const errorMessage = error.response ?
HTTP <span class="hljs-subst">${error.response.status}</span>: <span class="hljs-subst">${error.response.statusText}</span> : error.message;
return {
status: 'error',
error: errorMessage,
message: 'Failed to submit URLs'
};
}
}
module.exports = {
submitToIndexNow,
getDomainFromUrl
};
submit.js — Run the Submission
This is the file you'll run to actually send the URLs.
const { submitToIndexNow } = require('./indexnow');
// Your API key and the URLs you want to submit
const apiKey = '4262631fe57245bd9bd1cef01d1c3fa4';
const urls = [
'blog/passwordless-authentication-guide',
'blog/resistant-cryptography-migration'
];
const siteUrl = 'https://www.example.com';
// Calling the function to submit
submitToIndexNow(siteUrl, apiKey, urls)
.then(result => {
console.log('📬 Submission Result:', result);
})
.catch(err => {
console.error('❌ Unexpected error:', err.message);
});
Step 4: Verify Submission in Bing Webmaster Tools
After you've run the script, you can check if everything went through okay.
Head over to https://www.bing.com/webmasters/indexnow
If you haven't already, add and verify your domain.
You can use the URL inspection tool there to see the submission status.
References
API Endpoint