Strapi Webhooks — Content Type Events and Custom Webhook Payloads
In this tutorial, you will learn how to configure webhooks in Strapi — setting up event triggers for content type operations, customizing webhook payloads, verifying delivery securely, and handling failed webhook deliveries.
What You'll Learn
- What webhooks are and when to use them
- How to configure webhooks in the admin panel
- The available content type events for webhook triggers
- How to customize webhook payloads with custom headers
- How to verify webhook security on the receiving end
- How to handle webhook delivery failures and retries
- How to create webhooks programmatically
Why It Matters
Webhooks are how Strapi communicates with external services in real time. When content is created, updated, or deleted, webhooks send notifications to other systems — search indexes, CDN caches, static site builders, Slack channels, CRM systems. Without webhooks, these integrations require polling, which is inefficient and delayed.
Real-World Use
A content website uses a static site generator (Next.js SSG) to build pages from Strapi content. When an editor publishes a new article, a webhook notifies the build service, which triggers a new build. The updated site is live within 30 seconds. Without webhooks, the build would run on a timer (every hour), meaning content could be delayed by up to 60 minutes.
Learning Path
flowchart LR A["Custom Middleware"] --> B["Webhooks
-- You are here"]:::current B --> C["Strapi TypeScript"] C --> D["Testing"] classDef current fill:#4945ff,color:#fff,stroke-width:2px
Configuring Webhooks
Webhooks are configured in the admin panel:
Settings > Webhooks > Add Webhook
Name: "Deploy Production"
URL: https://api.example.com/webhooks/strapi
Events: Select which events trigger this webhook
Headers: Optional custom headers (e.g., Authorization, X-Secret)
Available events correspond to content type operations:
// Entry events
entry.create — An entry is created
entry.update — An entry is updated
entry.delete — An entry is deleted
entry.publish — An entry is published (Strapi 5)
entry.unpublish — An entry is unpublished (Strapi 5)
// Media events
media.create — A file is uploaded
media.update — A file is updated
media.delete — A file is deleted
You can select individual events or use wildcards like entry.* for all entry events.
Webhook Payload
When an event triggers a webhook, Strapi sends a POST request to the configured URL with this payload:
// Payload for entry.create
{
"event": "entry.create",
"createdAt": "2026-06-28T10:00:00.000Z",
"model": "article",
"uid": "api::article.article",
"entry": {
"id": 1,
"title": "New Article",
"content": "Article content here...",
"createdAt": "2026-06-28T10:00:00.000Z",
"updatedAt": "2026-06-28T10:00:00.000Z",
"publishedAt": null
}
}
// Payload for entry.update shows the updated entry
// Payload for entry.delete shows the deleted entry (before deletion)
// Payload for entry.publish includes publishedAt timestamp
The payload includes the full entry data. For large entries with rich text and relations, this payload can be substantial. The receiving service should parse and process the relevant fields.
Receiving Webhooks
Create an endpoint to receive webhook notifications:
// Express.js webhook receiver
const express = require("express");
const app = express();
app.post("/webhooks/strapi", express.json(), async (req, res) => {
const { event, model, entry } = req.body;
console.log(`Received webhook: ${event} on ${model}`);
switch (event) {
case "entry.create":
console.log(`New entry created: ${entry.id} - ${entry.title}`);
// Trigger static site rebuild
await triggerBuild();
break;
case "entry.update":
console.log(`Entry updated: ${entry.id}`);
// Invalidate CDN cache for this entry
await invalidateCache(entry.id);
break;
case "entry.delete":
console.log(`Entry deleted: ${entry.id}`);
// Remove from search index
await removeFromSearchIndex(entry.id);
break;
case "entry.publish":
console.log(`Entry published: ${entry.id}`);
// Notify subscribers
await notifySubscribers(entry);
break;
}
// Always respond quickly — Strapi waits for 200
res.status(200).json({ received: true });
});
The webhook endpoint should respond quickly (under 5 seconds). If the response takes too long, Strapi may time out and retry.
Webhook Security
Secure your webhooks with shared secrets:
// In Strapi webhook configuration:
// Headers:
// X-Webhook-Secret: your-shared-secret-key
// Receiving endpoint — verify the secret:
const express = require("express");
const app = express();
const WEBHOOK_SECRET = process.env.STRAPI_WEBHOOK_SECRET;
app.post("/webhooks/strapi", express.json(), (req, res) => {
const secret = req.headers["x-webhook-secret"];
const event = req.body.event;
// Verify the secret
if (secret !== WEBHOOK_SECRET) {
console.error("Invalid webhook secret");
return res.status(401).json({ error: "Invalid secret" });
}
// Process webhook
console.log(`Verified webhook: ${event}`);
res.status(200).json({ received: true });
});
Additional security measures:
- Use HTTPS for all webhook URLs
- Validate the source IP if Strapi has a static IP
- Validate the payload structure before processing
- Implement idempotency (process the same webhook only once)
Failed Delivery Handling
Strapi retries failed webhook deliveries:
// Strapi webhook retry behavior:
// - 5 retry attempts
// - Exponential backoff (increasing delays between retries)
// - Retries on timeout or non-200 response
// Check webhook delivery history:
// Settings > Webhooks > Click webhook name
// Shows: last delivery, last status, response body, error logs
For critical webhooks, implement a fallback mechanism:
// On the receiving end, acknowledge quickly:
app.post("/webhooks/strapi", (req, res) => {
// Acknowledge immediately
res.status(200).json({ received: true });
// Process asynchronously
processWebhookAsync(req.body).catch((err) => {
console.error("Webhook processing failed:", err);
// Log to error tracking service
});
});
async function processWebhookAsync(payload) {
const { event, model, entry } = payload;
// This runs after we've already responded
// Strapi won't retry since we returned 200
await triggerBuild();
await invalidateCache(entry.id);
await notifySubscribers(entry);
}
Webhooks via API
Create and manage webhooks programmatically:
// List all webhooks
GET /api/webhooks
// Requires admin permissions
// Create a webhook
POST /api/webhooks
{
"data": {
"name": "Deploy Webhook",
"url": "https://api.example.com/webhooks/strapi",
"headers": {
"X-Secret": "my-secret"
},
"events": [
"entry.publish",
"entry.unpublish"
]
}
}
// Update a webhook
PUT /api/webhooks/1
{
"data": {
"url": "https://new-url.example.com/webhooks",
"events": ["entry.create", "entry.update"]
}
}
// Delete a webhook
DELETE /api/webhooks/1
// Trigger a test event
POST /api/webhooks/1/trigger-test
Webhook management requires admin-level authentication.
Custom Webhook Payloads
Strapi does not support customizing the webhook payload format in the admin panel. For customized payloads, create a lifecycle hook that sends a custom HTTP request:
// src/api/article/content-types/article/lifecycle.js
module.exports = {
afterCreate: async (event) => {
const { result } = event;
// Send custom webhook with transformed payload
const customPayload = {
type: "article_created",
id: result.id,
title: result.title,
summary: result.content?.substring(0, 200),
url: `https://example.com/articles/${result.slug}`,
timestamp: new Date().toISOString(),
};
await fetch(process.env.CUSTOM_WEBHOOK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Custom-Secret": process.env.CUSTOM_WEBHOOK_SECRET,
},
body: JSON.stringify(customPayload),
}).catch((err) => {
strapi.log.error(`Custom webhook failed: ${err.message}`);
});
},
};
Common Webhook Use Cases
// 1. Static site rebuild trigger
// Trigger: entry.publish
// Payload: entry ID and slug
// Action: POST to Vercel/Netlify build hook
// 2. CDN cache purge
// Trigger: entry.update, entry.delete
// Action: Request CDN to purge specific URLs
// 3. Search index update
// Trigger: entry.create, entry.update, entry.delete
// Action: Add/update/remove entry from Algolia/Meilisearch
// 4. Slack notification
// Trigger: entry.publish
// Action: POST formatted message to Slack webhook
// 5. Email notification
// Trigger: entry.create (with specific conditions)
// Action: Send email to subscribers about new content
Common Mistakes
Not securing webhook endpoints. Without a shared secret or IP whitelist, anyone who discovers your webhook URL can send fake events. Always verify webhook authenticity.
Processing webhooks synchronously for slow operations. If your webhook handler takes 10 seconds to process, Strapi may time out and retry. Acknowledge immediately and process asynchronously.
Ignoring webhook delivery failures. Failed webhooks are shown in the admin panel but do not trigger alerts. Monitor webhook health separately.
Creating circular webhooks. If Service A sends a webhook to Service B, which updates content in Strapi, which sends another webhook to Service A, you create an infinite loop. Design webhook flows carefully.
Not testing webhook endpoints before going live. Use the "Trigger Test" button in the Strapi admin panel to verify the endpoint responds correctly before expecting production traffic.
Practice Questions
What events can trigger a Strapi webhook? Answer: entry.create, entry.update, entry.delete, entry.publish, entry.unpublish, media.create, media.update, media.delete. Wildcards like
entry.*are also supported.How do you secure a webhook endpoint? Answer: Add a shared secret as a custom header in the webhook configuration (e.g., X-Webhook-Secret). On the receiving end, verify the header value matches your secret.
What happens when a webhook delivery fails? Answer: Strapi retries up to 5 times with exponential backoff. After all retries fail, the webhook is marked as failed in the delivery history. You can check the logs and manually retry.
Challenge: Build a complete webhook integration: (1) Create a webhook endpoint (Express, Fastify, or Serverless function) that receives Strapi webhooks, (2) Configure a Strapi webhook that triggers on entry.publish for the Article content type, (3) Implement webhook secret verification, (4) On the receiving end: log the event, trigger a simulated static site rebuild, purge a simulated CDN cache, and send a Slack notification, (5) Add idempotency (process each webhook ID only once), (6) Test by publishing an article and verifying all actions are triggered.
FAQ
Mini Project
Your task: Build a real-time webhook integration system.
- Create a simple Node.js webhook receiver server (use Express).
- In Strapi, create 3 webhooks:
- "Build Trigger" -> Your receiver URL, events: entry.publish, entry.unpublish
- "Cache Purge" -> Same receiver URL, events: entry.update, entry.delete
- "All Events Logger" -> Same receiver URL, events: entry.* (for logging)
- Add a shared secret header to each webhook.
- On the receiver:
- Verify the webhook secret
- Log all received events to a file
- For publish events: simulate a build (console.log + setTimeout)
- For update events: simulate cache purge
- Return 200 quickly (within 100ms)
- Create, update, publish, and delete several articles in Strapi.
- Verify the receiver logs show all events and the correct actions.
What's Next
Now that you understand webhooks, proceed to Strapi with TypeScript to learn how to set up TypeScript in Strapi, generate type definitions, and write type-safe code. After that, explore Testing for unit and integration tests.
Related lessons:
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro