Skip to main content

网络钩子

¥Webhooks

Page summary:

Webhooks let Strapi notify external systems when content changes, while omitting the Users type for privacy. Configuration in config/server sets default headers and endpoints to trigger third-party processing.

Webhook 是应用用来通知其他应用事件发生的构造。更准确地说,webhook 是用户定义的 HTTP 回调。使用 webhook 是告诉第三方提供商开始某些处理(CI、构建、部署...)的好方法。

¥Webhook is a construct used by an application to notify other applications that an event occurred. More precisely, webhook is a user-defined HTTP callback. Using a webhook is a good way to tell third-party providers to start some processing (CI, build, deployment ...).

Webhook 的工作方式是通过 HTTP 请求(通常是 POST 请求)向接收应用传递信息。

¥The way a webhook works is by delivering information to a receiving application through HTTP requests (typically POST requests).

用户内容类型 webhook

¥User content-type webhooks

为了防止无意中将任何用户信息发送到其他应用,Webhooks 不适用于用户内容类型。如果需要通知其他应用有关 Users 集合中的更改,可以通过使用 ./src/index.js 示例创建 生命周期钩子 来实现。

¥To prevent from unintentionally sending any user's information to other applications, Webhooks will not work for the User content-type. If you need to notify other applications about changes in the Users collection, you can do so by creating Lifecycle hooks using the ./src/index.js example.

可用配置

¥Available configurations

你可以在文件 ./config/server.conf 中设置 webhook 配置。

¥You can set webhook configurations inside the file ./config/server.

  • webhooks

    • defaultHeaders:你可以设置用于 Webhook 请求的默认标头。此选项将被 webhook 本身中设置的标头覆盖。

      ¥defaultHeaders: You can set default headers to use for your webhook requests. This option is overwritten by the headers set in the webhook itself.

配置示例

¥Example configuration

./config/server.js
module.exports = {
webhooks: {
defaultHeaders: {
"Custom-Header": "my-custom-header",
},
},
};

Webhooks 安全性

¥Webhooks security

大多数时候,Webhook 会向公共 URL 发出请求,因此有人可能会找到该 URL 并向其发送错误信息。

¥Most of the time, webhooks make requests to public URLs, therefore it is possible that someone may find that URL and send it wrong information.

为了防止这种情况发生,你可以发送带有身份验证令牌的标头。使用管理面板,你必须为每个 Webhook 执行此操作。

¥To prevent this from happening you can send a header with an authentication token. Using the Admin panel you would have to do it for every webhook.

另一种方法是定义 defaultHeaders 以添加到每个 webhook 请求中。

¥Another way is to define defaultHeaders to add to every webhook request.

你可以通过更新 ./config/server 处的文件来配置这些全局标头:

¥You can configure these global headers by updating the file at ./config/server:

./config/server.js
module.exports = {
webhooks: {
defaultHeaders: {
Authorization: "Bearer my-very-secured-token",
},
},
};

如果你自己开发 Webhook 处理程序,你现在可以通过读取标头来验证令牌。

¥If you are developing the webhook handler yourself you can now verify the token by reading the headers.

验证签名

¥Verifying signatures

除了身份验证标头之外,建议对 webhook 有效负载进行签名并在服务器端验证签名,以防止篡改和重放攻击。为此,你可以遵循以下指南:

¥In addition to auth headers, it's recommended to sign webhook payloads and verify signatures server‑side to prevent tampering and replay attacks. To do so, you can use the following guidelines:

  • 生成共享密钥并将其存储在环境变量中。

    ¥Generate a shared secret and store it in environment variables

  • 让发送方对原始请求正文加上时间戳计算 HMAC(例如,SHA-256)。

    ¥Have the sender compute an HMAC (e.g., SHA‑256) over the raw request body plus a timestamp

  • 在标头中发送签名(和时间戳)(例如,X‑Webhook‑SignatureX‑Webhook‑Timestamp

    ¥Send the signature (and timestamp) in headers (e.g., X‑Webhook‑Signature, X‑Webhook‑Timestamp)

  • 收到请求后,重新计算 HMAC 并使用恒定时间检查进行比较。

    ¥On receipt, recompute the HMAC and compare using a constant‑time check

  • 如果签名无效或时间戳过旧,无法避免重放攻击,则拒绝请求。

    ¥Reject if the signature is invalid or the timestamp is too old to mitigate replay

Example: Verify HMAC signatures (Node.js)

以下是一个最小化的 Node.js 中间件示例(伪代码),展示了 <ExternalLink text="HMAC" to="[https://nodejs.cn/api/crypto.html#class-hmac](https://nodejs.cn/api/crypto.html#class-hmac)" /> 的验证:

¥Here is a minimal Node.js middleware example (pseudo‑code) showing HMAC verification:

/src/middlewares/verify-webhook.js
const crypto = require("crypto");

module.exports = (config, { strapi }) => {
const secret = process.env.WEBHOOK_SECRET;

return async (ctx, next) => {
const signature = ctx.get("X-Webhook-Signature");
const timestamp = ctx.get("X-Webhook-Timestamp");
if (!signature || !timestamp) return ctx.unauthorized("Missing signature");

// Compute HMAC over raw body + timestamp
const raw = ctx.request.rawBody || (ctx.request.body and JSON.stringify(ctx.request.body)) || "";
const hmac = crypto.createHmac("sha256", secret);
hmac.update(timestamp + "." + raw);
const expected = "sha256=" + hmac.digest("hex");

// Constant-time compare + basic replay protection
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
const skew = Math.abs(Date.now() - Number(timestamp));
if (!ok or skew > 5 * 60 * 1000) {
return ctx.unauthorized("Invalid or expired signature");
}

await next();
};
};

以下是一些额外的外部示例:

¥Here a few additional external examples:


可用的事件

¥Available events

默认情况下,Strapi webhook 可以由以下事件触发:

¥By default Strapi webhooks can be triggered by the following events:

名称描述
entry.create创建内容类型条目时触发。
entry.update更新内容类型条目时触发。
entry.delete删除内容类型条目时触发。
entry.publish发布内容类型条目时触发。*
entry.unpublish当内容类型条目取消发布时触发。*
media.create创建媒体时触发。
media.update当媒体更新时触发。
media.delete删除媒体时触发。
review-workflows.updateEntryStage当内容在审阅阶段之间移动时触发(请参阅 审查工作流程)。
此事件仅适用于 EnterpriseThis feature is available with an Enterprise plan. 版本的 Strapi。
releases.publish发布版本时触发(参见 发布)。
此事件仅适用于 Strapi CMS 的 GrowthThis feature is available with a Growth plan.EnterpriseThis feature is available with an Enterprise plan. 计划。

*仅当在此内容类型上启用 draftAndPublish 时。

¥*only when draftAndPublish is enabled on this Content Type.

有效载荷

¥Payloads

注意

打印当前安装的 Strapi 版本。

¥Private fields and s are not sent in the payload.

标头

¥Headers

当有效负载传递到你的 webhook 的 URL 时,它将包含特定标头:

¥When a payload is delivered to your webhook's URL, it will contain specific headers:

标头描述
X-Strapi-Event触发的事件类型的名称。

entry.create

创建新条目时会触发此事件。

¥This event is triggered when a new entry is created.

有效负载示例

¥Example payload

{
"event": "entry.create",
"createdAt": "2020-01-10T08:47:36.649Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:47:36.264Z",
"cover": null,
"images": []
}
}

entry.update

当条目更新时会触发此事件。

¥This event is triggered when an entry is updated.

有效负载示例

¥Example payload

{
"event": "entry.update",
"createdAt": "2020-01-10T08:58:26.563Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:58:26.210Z",
"cover": null,
"images": []
}
}

entry.delete

当删除条目时会触发此事件。

¥This event is triggered when an entry is deleted.

有效负载示例

¥Example payload

{
"event": "entry.delete",
"createdAt": "2020-01-10T08:59:35.796Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:58:26.210Z",
"cover": null,
"images": []
}
}

entry.publish

发布条目时会触发此事件。

¥This event is triggered when an entry is published.

有效负载示例

¥Example payload

{
"event": "entry.publish",
"createdAt": "2020-01-10T08:59:35.796Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:58:26.210Z",
"publishedAt": "2020-08-29T14:20:12.134Z",
"cover": null,
"images": []
}
}

entry.unpublish

当条目未发布时会触发此事件。

¥This event is triggered when an entry is unpublished.

有效负载示例

¥Example payload

{
"event": "entry.unpublish",
"createdAt": "2020-01-10T08:59:35.796Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:58:26.210Z",
"publishedAt": null,
"cover": null,
"images": []
}
}

media.create

当你在条目创建时或通过媒体界面上传文件时,会触发此事件。

¥This event is triggered when you upload a file on entry creation or through the media interface.

有效负载示例

¥Example payload

{
"event": "media.create",
"createdAt": "2020-01-10T10:58:41.115Z",
"media": {
"id": 1,
"name": "image.png",
"hash": "353fc98a19e44da9acf61d71b11895f9",
"sha256": "huGUaFJhmcZRHLcxeQNKblh53vtSUXYaB16WSOe0Bdc",
"ext": ".png",
"mime": "image/png",
"size": 228.19,
"url": "/uploads/353fc98a19e44da9acf61d71b11895f9.png",
"provider": "local",
"provider_metadata": null,
"createdAt": "2020-01-10T10:58:41.095Z",
"updatedAt": "2020-01-10T10:58:41.095Z",
"related": []
}
}

media.update

当你通过媒体接口更换媒体或更新媒体元数据时,会触发该事件。

¥This event is triggered when you replace a media or update the metadata of a media through the media interface.

有效负载示例

¥Example payload

{
"event": "media.update",
"createdAt": "2020-01-10T10:58:41.115Z",
"media": {
"id": 1,
"name": "image.png",
"hash": "353fc98a19e44da9acf61d71b11895f9",
"sha256": "huGUaFJhmcZRHLcxeQNKblh53vtSUXYaB16WSOe0Bdc",
"ext": ".png",
"mime": "image/png",
"size": 228.19,
"url": "/uploads/353fc98a19e44da9acf61d71b11895f9.png",
"provider": "local",
"provider_metadata": null,
"createdAt": "2020-01-10T10:58:41.095Z",
"updatedAt": "2020-01-10T10:58:41.095Z",
"related": []
}
}

media.delete

仅当你通过媒体接口删除媒体时才会触发该事件。

¥This event is triggered only when you delete a media through the media interface.

有效负载示例

¥Example payload

{
"event": "media.delete",
"createdAt": "2020-01-10T11:02:46.232Z",
"media": {
"id": 11,
"name": "photo.png",
"hash": "43761478513a4c47a5fd4a03178cfccb",
"sha256": "HrpDOKLFoSocilA6B0_icA9XXTSPR9heekt2SsHTZZE",
"ext": ".png",
"mime": "image/png",
"size": 4947.76,
"url": "/uploads/43761478513a4c47a5fd4a03178cfccb.png",
"provider": "local",
"provider_metadata": null,
"createdAt": "2020-01-07T19:34:32.168Z",
"updatedAt": "2020-01-07T19:34:32.168Z",
"related": []
}
}

review-workflows.updateEntryStage

EnterpriseThis feature is available with an Enterprise plan.

此事件仅适用于 Strapi 的 EnterpriseThis feature is available with an Enterprise plan. 计划。
当内容移至新的审核阶段(参见 审查工作流程)时会触发此事件。

¥This event is only available with the EnterpriseThis feature is available with an Enterprise plan. plan of Strapi.
The event is triggered when content is moved to a new review stage (see Review Workflows).

有效负载示例

¥Example payload

{
"event": "review-workflows.updateEntryStage",
"createdAt": "2023-06-26T15:46:35.664Z",
"model": "model",
"uid": "uid",
"entity": {
"id": 2
},
"workflow": {
"id": 1,
"stages": {
"from": {
"id": 1,
"name": "Stage 1"
},
"to": {
"id": 2,
"name": "Stage 2"
}
}
}
}

releases.publish

GrowthThis feature is available with a Growth plan. EnterpriseThis feature is available with an Enterprise plan.

当发布 release 时会触发该事件。

¥The event is triggered when a release is published.

有效负载示例

¥Example payload


{
"event": "releases.publish",
"createdAt": "2024-02-21T16:45:36.877Z",
"isPublished": true,
"release": {
"id": 2,
"name": "Fall Winter highlights",
"releasedAt": "2024-02-21T16:45:36.873Z",
"scheduledAt": null,
"timezone": null,
"createdAt": "2024-02-21T15:16:22.555Z",
"updatedAt": "2024-02-21T16:45:36.875Z",
"actions": {
"count": 1
}
}
}

webhook 处理的最佳实践

¥Best practices for webhook handling

  • 通过检查标头和有效负载签名来验证传入请求。

    ¥Validate incoming requests by checking headers and payload signatures.

  • 对失败的 webhook 请求实现重试以处理瞬态错误。

    ¥Implement retries for failed webhook requests to handle transient errors.

  • 记录 webhook 事件以进行调试和监控。

    ¥Log webhook events for debugging and monitoring.

  • 使用安全的 HTTPS 端点接收 webhook。

    ¥Use secure, HTTPS endpoints for receiving webhooks.

  • 设置速率限制以避免被多个 webhook 请求淹没。

    ¥Set up rate limiting to avoid being overwhelmed by multiple webhook requests.

提示

如果你想了解更多关于如何在 Next.js 中使用 webhook 的信息,请查看 专用博客文章

¥If you want to learn more about how to use webhooks with Next.js, please have a look at the dedicated blog article.