使用 Notion 的 Webhook 功能通知博客更新

Post Details
Category
开发
Tags
Notion,CMS
Published
Views
Loading...
Loading...


Webhooks
Webhooks let your integration receive real-time updates from Notion. Whenever a page or database changes, Notion sends a secure HTTP POST request to your webhook endpoint. This allows your application to respond to workspace activity as it happens — whether that's syncing data, triggering automation...
page.properties_updated | Triggered when a page's property is updated.
当页面的属性更新时触发。 |
database.content_updated | Triggered when a database's content is updated— for example,当数据库内容更新时触发 — 例如,adding or removing a child page.添加或删除子页面。 |
comment.created | Triggered when a new comment or suggested edit is added to a page or block
当向页面或区块添加新评论或建议的编辑时触发 |
page.properties_updated | Triggered when a page's property is updated.
更新页面的属性时触发。 |
{ // 事件发生时间 "timestamp": "2024-12-05T20:46:45.854Z", // 事件类型 "type": "comment.created", // 评论所属页面的 id "data": { "page_id": "0ef104cd-477e-80e1-8571-cfd10e92339a", } }
{ "id": "c6780f24-10b7-4f42-a6fd-230b6cf7ad69", "timestamp": "2024-12-05T20:46:45.854Z", "workspace_id": "13950b26-c203-4f3b-b97d-93ec06319565", "workspace_name": "Quantify Labs", "subscription_id": "29d75c0d-5546-4414-8459-7b7a92f1fc4b", "integration_id": "0ef2e755-4912-8096-91c1-00376a88a5ca", "type": "comment.created", "authors": [ { "id": "c7c11cca-1d73-471d-9b6e-bdef51470190", "type": "person" } ], "accessible_by": [ { "id": "556a1abf-4f08-40c6-878a-75890d2a88ba", "type": "person" } ], "attempt_number": 1, "entity": { "id": "153104cd-477e-80ca-8f75-001d9e2b6839", "type": "comment" }, "data": { "page_id": "0ef104cd-477e-80e1-8571-cfd10e92339a", "parent": { "id": "0ef104cd-477e-80e1-8571-cfd10e92339a", "type": "page" } } }
{ // 事件发生时间 "timestamp": "2024-12-05T20:46:45.854Z", // 事件类型 "type": "page.properties_updated", // 发生属性变化的页面所属的database id "data": { "parent": { "id": "13950b26-c203-4f3b-b97d-93ec06319565", "type": "space" } } }
{ "id": "1782edd6-a853-4d4a-b02c-9c8c16f28e53", "timestamp": "2024-12-05T23:57:05.379Z", "workspace_id": "13950b26-c203-4f3b-b97d-93ec06319565", "workspace_name": "Quantify Labs", "subscription_id": "29d75c0d-5546-4414-8459-7b7a92f1fc4b", "integration_id": "0ef2e755-4912-8096-91c1-00376a88a5ca", "type": "page.properties_updated", "authors": [ { "id": "c7c11cca-1d73-471d-9b6e-bdef51470190", "type": "person" } ], "accessible_by": [ { "id": "556a1abf-4f08-40c6-878a-75890d2a88ba", "type": "person" } ], "attempt_number": 1, "entity": { "id": "153104cd-477e-809d-8dc4-ff2d96ae3090", "type": "page" }, "data": { "parent": { "id": "13950b26-c203-4f3b-b97d-93ec06319565", "type": "space" } } }


verification_token,证明 Notion 可以成功到达您的终端节点。export async function POST(req: NextRequest) { const rawBody = await req.text(); const payload = JSON.parse(rawBody); if (payload.verification_token) { console.log("verification_token:", payload.verification_token); return new Response(null, { status: 200 }); } }



NOTION_WEBHOOK_SECRET=secret_xxxx
X-Notion-Signature 标头,其中包含请求正文的 HMAC-SHA256 哈希值。获取该签名verification_token 计算出签名import { NextRequest, NextResponse } from "next/server"; import { revalidateTag } from "next/cache"; import crypto from "crypto"; // verify the request from notion const verifyNotionRequest = (req: NextRequest, body: string) => { // get the signing secret from the environment variables const signingSecret = process.env.NOTION_WEBHOOK_SECRET; if (!signingSecret) { console.error("NOTION_WEBHOOK_SECRET is not set"); return false; } // get the notion signature from the request headers const notionSignature = req.headers.get("x-notion-signature"); if (!notionSignature) { console.error("x-notion-signature is not set"); return false; } // compute the hmac of the request body const hmac = crypto.createHmac("sha256", signingSecret); hmac.update(body); const computedSignature = `sha256=${hmac.digest("hex")}`; // compare the computed signature with the notion signature try { return crypto.timingSafeEqual( Buffer.from(computedSignature), Buffer.from(notionSignature) ); } catch (error) { console.error("Error verifying notion request", error); return false; } };
export async function POST(req: NextRequest) { try { const rawBody = await req.text(); // validate if the request is from notion const isValid = verifyNotionRequest(req, rawBody); if (!isValid) { console.error("Notion webhook signature verification failed"); return NextResponse.json({ error: "invalid signature" }, { status: 401 }); } // log the webhook event (for debugging) console.log("received Notion webhook:", { eventType: payload.type, timestamp: new Date().toISOString(), databaseId: payload.database?.id || "unknown", }); // invalidate the cache tag revalidateTag("notion-database"); console.log("invalidated cache tag: notion-database"); // return the success response return NextResponse.json({ success: true, message: "database cache revalidated successfully", timestamp: new Date().toISOString(), }); } catch (error) { // error handling console.error("error handling Notion webhook:", error); return NextResponse.json( { error: "internal server error", message: (error as Error).message }, { status: 500 } ); } }