Setting Up Webhooks and Integrations for Sanity
Sanity sends webhooks when documents change in Content Lake. Primary way to invalidate ISR cache, synchronize with search indexes and notify team.
Creating a Webhook in Sanity
In sanity.io → project → API → Webhooks → Add Webhook:
-
URL:
https://yoursite.com/api/webhooks/sanity - Trigger on: Create, Update, Delete
-
Filter:
_type == "post"(only needed types) - Secret: random string to verify signature
-
Projection:
{ _id, _type, "slug": slug.current }(only needed data)
Webhook Handler in Next.js
// app/api/webhooks/sanity/route.ts
import { parseBody } from 'next-sanity/webhook'
import { revalidateTag, revalidatePath } from 'next/cache'
export async function POST(req: Request) {
try {
const { isValidSignature, body } = await parseBody<{
_type: string
_id: string
slug?: string
}>(req, process.env.SANITY_WEBHOOK_SECRET!)
if (!isValidSignature) {
return Response.json({ message: 'Invalid signature' }, { status: 401 })
}
const { _type, slug } = body
revalidateTag(_type)
const pathMap: Record<string, string> = {
post: `/blog/${slug}`,
page: `/${slug}`,
}
if (pathMap[_type] && slug) {
revalidatePath(pathMap[_type])
}
return Response.json({ revalidated: true })
} catch (err) {
return Response.json({ message: 'Webhook error' }, { status: 500 })
}
}
Slack Notifications
// Notify when article is published
// sanity.io → Webhooks → Filter: _type == "post" && defined(publishedAt)
// app/api/webhooks/sanity-slack/route.ts
export async function POST(req: Request) {
const { isValidSignature, body } = await parseBody(req, process.env.SANITY_WEBHOOK_SECRET!)
if (!isValidSignature) return Response.json({ error: 'Unauthorized' }, { status: 401 })
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: 'POST',
body: JSON.stringify({
text: `📝 New article published: *${body.title}*`,
attachments: [{
text: `https://yoursite.com/blog/${body.slug}`,
}],
}),
})
return Response.json({ notified: true })
}
Timeline
Setting up webhooks for ISR and search indexing — 0.5–1 day.







