IndexNOW notify (integrator endpoint)
IndexNOW notify (integrator endpoint)
When Blog or Market content is published, updated, unpublished, or deleted, Mailoo can POST identifiers to your site. You build the public page URLs and submit them to IndexNow. Mailoo does not call IndexNOW directly and does not send absolute URLs.
Publish and catalog writes in Mailoo always succeed even if your endpoint is missing or returns an error (fire-and-forget). Mailoo records the outcome on the content row as indexNowStatus.
Related: Blog CMS --- blog-headless-cms{.interpreted-text role="doc"}; Market catalog --- market-catalog-external-api{.interpreted-text role="doc"}; Next.js blog example --- blog-nextjs-example{.interpreted-text role="doc"}; delivery webhook HMAC (same signing formula) --- transactional-email{.interpreted-text role="doc"}.
Architecture
- Trigger: After a successful Blog article or Market product (or published-product variant) write.
- Payload: Project/integration/content ids, optional blog
slug,locales, andaction--- no page URLs. - Your job: Map ids → public URLs → IndexNOW API with your key.
- Status on content:
PENDING→NOTIFIED|FAILED, orSKIPPEDwhenindexNowis not configured.nullmeans never attempted (e.g. draft-only).
Configuration
Per BLOG or MARKET integration:
- Dashboard → Connection & settings → IndexNOW notify
- Or
PATCH /api/v1/projects/{uid}/integrations/{id}with body:
{
"indexNow": {
"notifyUrl": "https://www.example.com/api/indexnow/notify",
"secret": "your-signing-secret"
}
}
Send "indexNow": null to clear. Sanitized API responses expose notifyUrl and hasSecret (never the raw secret).
If notifyUrl / secret are unset, Mailoo sets indexNowStatus to ``SKIPPED`` and does not HTTP-call your site.
Endpoint contract
Method / URL: POST to the configured notifyUrl.
Headers:
Content-Type: application/jsonX-Mailoo-Event: indexnow.notifyX-Mailoo-Signature: t={unixSeconds},v1={hex}---v1is HMAC-SHA256 of"{timestamp}.{rawBody}"usingindexNow.secret(same formula as transactional delivery webhooks).
Body (JSON):
{
"event": "indexnow.notify",
"kind": "blog_post",
"action": "published",
"projectUid": "…",
"integrationId": "…",
"contentId": "…",
"slug": "my-post",
"locales": ["en", "de"],
"timestamp": "2026-08-03T09:00:00.000Z"
}
Fields by kind
kind---blog_postormarket_productcontentId--- article id or product idslug--- article slug (blog only; omitted for market)locales--- blog:enplus keys from articlelocalesJSON; market:defaultCatalogLocaleorenaction---published,updated,unpublished, ordeleted
When Mailoo fires
- Create with
PUBLISHED→published - Update that becomes or stays
PUBLISHED→published/updated - Status leaves
PUBLISHED→unpublished - Delete while published →
deleted(no status row update afterward) - Draft-only writes → no notify;
indexNowStatusstaysnull
Expected responses
- 2xx --- accepted (you may queue IndexNOW asynchronously). Mailoo sets
NOTIFIED. - 404 / 5xx / network / timeout --- Mailoo sets
FAILED. Content publish already committed. - Missing config ---
SKIPPEDwithout a request.
indexNowStatus values
PENDING--- notify in flightNOTIFIED--- integrator returned 2xxSKIPPED--- noindexNowconfig on the integrationFAILED--- request failed or non-2xxnull--- not applicable / never attempted
Integrator responsibilities
- Verify
X-Mailoo-Signature(reject invalid signatures). - Resolve public URL(s) from
kind,contentId/slug, andlocales. - Call IndexNOW with your key (and host key file as required by IndexNOW).
- Return 2xx quickly; do heavy work asynchronously if needed.
Next.js App Router sketch
// app/api/indexnow/notify/route.ts
import { createHmac, timingSafeEqual } from 'node:crypto'
import { NextRequest, NextResponse } from 'next/server'
const SECRET = process.env.MAILOO_INDEXNOW_SECRET!
function verify(signatureHeader: string | null, body: string): boolean {
if (!signatureHeader) return false
const m = /^t=(\d+),v1=([a-f0-9]+)$/i.exec(signatureHeader.trim())
if (!m) return false
const [, t, v1] = m
const expected = createHmac('sha256', SECRET)
.update(`${t}.${body}`)
.digest('hex')
try {
return timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'))
} catch {
return false
}
}
export async function POST(req: NextRequest) {
const raw = await req.text()
if (!verify(req.headers.get('x-mailoo-signature'), raw)) {
return NextResponse.json({ error: 'invalid signature' }, { status: 401 })
}
const payload = JSON.parse(raw) as {
kind: string
action: string
contentId: string
slug?: string
locales: string[]
}
// Build URLs from payload, then POST to https://api.indexnow.org/indexnow
void payload
return NextResponse.json({ ok: true })
}
Prerequisites
- BLOG or MARKET integration
indexNow.notifyUrl+secretconfigured (optional; without it status isSKIPPED)- Your site implements the notify endpoint and IndexNOW key hosting