How to Set Up the Revalidation Webhook
Configure automatic on-demand revalidation when content is published in Strapi.
Overview
Instead of waiting for cache expiration, revalidate content immediately when it's published in Strapi. This provides:
- ✅ Instant content updates
- ✅ No manual cache clearing
- ✅ Efficient ISR workflow
- ✅ Type-safe webhook handling
Step 1: Create Revalidation Route
Create app/api/revalidate/route.ts:
import { createStrapiRevalidator } from 'strapi-nextgen-framework';
// Create revalidation handler
const handler = createStrapiRevalidator({
secret: process.env.REVALIDATION_SECRET!,
tagMap: {
// Map Strapi content types to cache tags
'api::article.article': 'articles',
'api::page.page': 'pages',
'api::global.global': 'global',
},
});
export const POST = handler;Step 2: Add Environment Variable
Add to .env.local:
REVALIDATION_SECRET=your-random-secret-minimum-32-charactersGenerate a secure secret:
openssl rand -base64 32Step 3: Configure Strapi Webhook
In your Strapi admin panel:
- Go to Settings → Webhooks
- Click Create new webhook
- Configure:
{
"name": "Next.js Revalidation",
"url": "https://your-site.com/api/revalidate",
"headers": {
"Authorization": "Bearer your-revalidation-secret"
},
"events": [
"entry.create",
"entry.update",
"entry.delete",
"entry.publish",
"entry.unpublish"
]
}For local development:
URL: http://localhost:3000/api/revalidateUse ngrok (opens in a new tab) to expose your local server for testing:
ngrok http 3000
# Use the https URL in Strapi webhookStep 4: Tag Your Data Fetching
Add cache tags to your data fetching:
import { unstable_cache } from 'next/cache';
import { strapi } from '@/lib/strapi';
import { GetArticlesDocument } from '@/graphql/generated';
export const getArticles = unstable_cache(
async () => {
const data = await strapi.getCollection('articles', GetArticlesDocument);
return data.articles?.data;
},
['articles-list'],
{
tags: ['articles'], // 👈 This tag matches tagMap
revalidate: 3600, // Fallback: revalidate hourly
}
);How It Works
1. Content Published in Strapi
Editor clicks "Publish" on an article.
2. Webhook Triggered
Strapi sends POST to /api/revalidate:
{
"event": "entry.publish",
"model": "api::article.article",
"entry": {
"id": 1,
"slug": "my-article",
// ... other fields
}
}3. Revalidation Handler
The handler:
- ✅ Validates the secret from
Authorizationheader - ✅ Extracts the model from webhook payload
- ✅ Looks up cache tag from
tagMap - ✅ Calls
revalidateTag('articles') - ✅ Returns 200 OK
4. Next.js Revalidation
Next.js:
- ✅ Invalidates all cache entries with tag
articles - ✅ Re-fetches data on next request
- ✅ Updates static pages in background
Tag Map Configuration
Basic Mapping
Map Strapi model to cache tag:
tagMap: {
'api::article.article': 'articles',
'api::page.page': 'pages',
}Multiple Tags
Revalidate multiple tags:
tagMap: {
'api::article.article': ['articles', 'blog', 'feed'],
'api::global.global': ['global', 'header', 'footer'],
}Auto-Generation
Omit tagMap for automatic tag generation:
const handler = createStrapiRevalidator({
secret: process.env.REVALIDATION_SECRET!,
// No tagMap - automatically generates 'articles', 'pages', etc.
});This generates tags like:
api::article.article→articleapi::page.page→pageapi::global.global→global
Advanced Patterns
Conditional Revalidation
Revalidate based on entry data:
import { NextRequest } from 'next/server';
import { revalidateTag } from 'next/cache';
export async function POST(request: NextRequest) {
// Validate secret
const authHeader = request.headers.get('authorization');
const secret = authHeader?.replace('Bearer ', '');
if (secret !== process.env.REVALIDATION_SECRET) {
return new Response('Unauthorized', { status: 401 });
}
// Parse webhook payload
const payload = await request.json();
const { model, entry, event } = payload;
// Only revalidate on publish
if (event !== 'entry.publish') {
return new Response('Skipped', { status: 200 });
}
// Revalidate specific article page
if (model === 'api::article.article') {
revalidateTag('articles');
revalidateTag(`article-${entry.slug}`);
}
return new Response('Revalidated', { status: 200 });
}Path Revalidation
Revalidate specific paths instead of tags:
import { revalidatePath } from 'next/cache';
// Revalidate homepage
revalidatePath('/');
// Revalidate specific article
revalidatePath(`/blog/${entry.slug}`);
// Revalidate all blog pages
revalidatePath('/blog', 'layout');Logging & Monitoring
Add logging for debugging:
const handler = createStrapiRevalidator({
secret: process.env.REVALIDATION_SECRET!,
tagMap: {
'api::article.article': 'articles',
},
onRevalidate: (tag) => {
console.log(`✅ Revalidated cache tag: ${tag}`);
},
onError: (error) => {
console.error('❌ Revalidation error:', error);
},
});Webhook Verification
Add additional security:
import crypto from 'crypto';
export async function POST(request: NextRequest) {
// Verify webhook signature (if Strapi supports it)
const signature = request.headers.get('x-strapi-signature');
const rawBody = await request.text();
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(rawBody)
.digest('hex');
if (signature !== expectedSignature) {
return new Response('Invalid signature', { status: 401 });
}
// Process webhook...
}Testing Revalidation
Manual Test
curl -X POST http://localhost:3000/api/revalidate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-secret" \
-d '{
"event": "entry.publish",
"model": "api::article.article",
"entry": {
"id": 1,
"slug": "test-article"
}
}'Expected response:
{
"revalidated": true,
"tag": "articles"
}Test in Strapi
- Go to Settings → Webhooks
- Find your webhook
- Click Trigger
- Check Next.js logs for revalidation
Automated Test
import { test, expect } from '@playwright/test';
test('revalidation updates content', async ({ page, request }) => {
// Visit page and get initial content
await page.goto('/blog/test-article');
const initialTitle = await page.locator('h1').textContent();
// Update content in Strapi (via API)
await request.put('http://localhost:1337/api/articles/1', {
data: {
title: 'Updated Title',
},
});
// Trigger revalidation
await request.post('http://localhost:3000/api/revalidate', {
headers: {
'Authorization': `Bearer ${process.env.REVALIDATION_SECRET}`,
'Content-Type': 'application/json',
},
data: {
event: 'entry.update',
model: 'api::article.article',
entry: { slug: 'test-article' },
},
});
// Reload page and verify update
await page.reload();
const updatedTitle = await page.locator('h1').textContent();
expect(updatedTitle).not.toBe(initialTitle);
expect(updatedTitle).toBe('Updated Title');
});Troubleshooting
Webhook Not Triggering
Problem: Content published but cache not revalidated
Solutions:
- Check webhook is enabled in Strapi
- Verify URL is correct (use ngrok for local)
- Check Strapi webhook logs for errors
- Ensure events are selected in webhook config
401 Unauthorized
Problem: Webhook returns 401
Solutions:
- Check
Authorizationheader format:Bearer YOUR_SECRET - Verify secret matches in .env.local
- Ensure no trailing spaces in secret
- Check request headers in Strapi webhook config
Cache Not Invalidating
Problem: Webhook succeeds but content doesn't update
Solutions:
- Verify cache tag matches in
tagMapandunstable_cache - Check you're using tags, not just time-based revalidation
- Clear .next folder:
rm -rf .next - Ensure you're in production mode:
npm run build && npm start
Too Many Revalidations
Problem: Webhook floods with requests
Solutions:
- Filter events - only use
entry.publish - Add rate limiting to webhook route
- Use conditional logic to skip unnecessary revalidations
- Consider debouncing multiple updates
Best Practices
1. Secure Your Webhook
Always use strong secrets:
# Generate 256-bit secret
openssl rand -base64 322. Use Specific Tags
Tag granularly for targeted revalidation:
// ❌ Too broad
revalidateTag('content');
// ✅ Specific
revalidateTag('articles');
revalidateTag(`article-${slug}`);3. Handle Errors Gracefully
Don't let revalidation errors crash your site:
try {
revalidateTag('articles');
} catch (error) {
console.error('Revalidation failed:', error);
// Site continues working with cached data
}4. Monitor Webhook Activity
Log all revalidations:
console.log({
timestamp: new Date().toISOString(),
event: payload.event,
model: payload.model,
tag: resolvedTag,
});5. Use HTTPS in Production
Never use HTTP webhooks in production:
❌ http://your-site.com/api/revalidate
✅ https://your-site.com/api/revalidateNext Steps
- Fetch Global Data - Revalidate headers/footers
- Preview Mode - Preview before revalidation
- Error Handling - Handle revalidation failures