Skip to content

Strapi Webhooks — Content Type Events and Custom Webhook Payloads

DodaTech Updated 2026-06-28 9 min read

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:

  1. Use HTTPS for all webhook URLs
  2. Validate the source IP if Strapi has a static IP
  3. Validate the payload structure before processing
  4. 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

  1. 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.

  2. 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.

  3. Ignoring webhook delivery failures. Failed webhooks are shown in the admin panel but do not trigger alerts. Monitor webhook health separately.

  4. 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.

  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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

Can I send webhooks to multiple URLs for the same event?

Each webhook configuration sends to one URL. Create multiple webhooks (one per URL) with the same event selection to send to multiple endpoints.

What is the maximum webhook payload size?

Strapi does not document a specific limit. The payload is the full entry JSON. For entries with large richtext fields or many relations, the payload can be several MB. Consider using lifecycle hooks with custom payloads for large entries.

Can I delay webhook delivery?

Strapi sends webhooks immediately when the event occurs. There is no built-in delay or batching. For delayed or batched webhooks, queue the events on the receiving end.

How do I view webhook delivery history?

In the admin panel, go to Settings > Webhooks and click on a webhook name. The delivery history shows the last deliveries, their status codes, response bodies, and any errors.

Can webhooks be triggered for specific content types only?

Yes. When configuring a webhook, you can select specific content types. The webhook only fires for events on the selected types. Leave it empty to trigger for all types.

Mini Project

Your task: Build a real-time webhook integration system.

  1. Create a simple Node.js webhook receiver server (use Express).
  2. 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)
  3. Add a shared secret header to each webhook.
  4. 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)
  5. Create, update, publish, and delete several articles in Strapi.
  6. 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:

  • API Customization — Custom webhook endpoints
  • Node.js — Building webhook receivers
  • REST API — How webhooks relate to API events

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro