Gareth Wilson Gareth Wilson

Guide to Notion Webhooks: Features and Best Practices

Published


Notion has become the go-to workspace for teams managing everything from product roadmaps and wikis to project databases and company knowledge bases. With millions of users relying on it as a central hub, the ability to react to changes in real-time is critical for keeping external systems in sync, triggering automations, and building integrations that feel seamless.

Notion's integration webhooks enable exactly this. Instead of repeatedly polling the Notion API to check if anything has changed, Notion will tell you the moment something important happens, sending secure HTTP POST requests to your endpoint whenever pages, databases, or comments change.

This guide covers everything you need to know about Notion webhooks: their features, how to configure them, best practices for production deployments, and the common pain points developers face along with solutions to address them.

What are Notion webhooks?

Notion webhooks are HTTP callbacks that deliver event notifications to external endpoints whenever content changes within a Notion workspace. When a page is created, a database schema is modified, or a comment is added, Notion sends a JSON payload containing event metadata to URLs you configure. This enables integration with project management tools, CRMs, custom automation pipelines, and any service that can receive HTTP requests.

Webhooks in Notion exist in two distinct contexts:

  • Integration webhooks — The primary webhook system for developers building integrations with the Notion API. These are configured through your integration settings and deliver events for pages, databases, data sources, and comments.
  • Webhook actions — A no-code feature available in Notion buttons, database buttons, and database automations that sends HTTP POST requests to a specified URL. These are designed for simpler automation workflows and are available on paid plans.

This guide focuses primarily on integration webhooks, as they're the most flexible and commonly used webhook integration for developers.

Notion webhook features

FeatureDetails
Webhook configurationIntegration settings UI
Hashing algorithmHMAC-SHA256
Signature headerX-Notion-Signature
Retry logicUp to 8 retries with exponential backoff over ~24 hours
Delivery guaranteeAt-most-once
Event delivery timingMost events within 1 minute; all within 5 minutes
Event aggregationHigh-frequency events batched within short time windows
Payload styleSparse (IDs and metadata only; full content requires API follow-up)
AuthenticationHMAC-SHA256 signature verification via verification_token
Manual retryNot available
Browsable logNot available
SSL requiredYes — endpoints must be HTTPS and publicly accessible

Supported event types

Notion webhooks notify you about changes to pages, databases, data sources, and comments. Each event represents a meaningful change to content in a workspace. The events themselves do not contain the full content that changed — they act as signals, and it's up to your integration to follow up with a call to the Notion API to retrieve the latest content.

TypeDescriptionAggregated?
page.content_updatedTriggered when the content of a page changes — e.g. adding or removing a block.Yes
page.createdTriggered when a new page is created.Yes
page.deletedTriggered when a page is moved to the trash.Yes
page.lockedTriggered when a page is locked from editing.No
page.movedTriggered when a page is moved to another location.Yes
page.properties_updatedTriggered when a page's property is updated.Yes
page.undeletedTriggered when a page is restored from the trash.Yes
page.unlockedTriggered when a page is unlocked.No
database.createdTriggered when a new database is created.Yes
database.content_updatedTriggered when a database's content is updated. Deprecated in 2025-09-03 API version.Yes
database.deletedTriggered when a database is moved to the trash.Yes
database.movedTriggered when a database is moved to another location.Yes
database.schema_updatedTriggered when a database's schema is updated. Deprecated in 2025-09-03 API version.Yes
database.undeletedTriggered when a database is restored from the trash.Yes
data_source.content_updatedTriggered when a data source's content is updated. New in 2025-09-03.Yes
data_source.createdTriggered when a new data source is created within a database. New in 2025-09-03.Yes
data_source.deletedTriggered when a data source is moved to the trash. New in 2025-09-03.Yes
data_source.movedTriggered when a data source is moved to another database. New in 2025-09-03.Yes
data_source.schema_updatedTriggered when a data source's schema is updated. New in 2025-09-03.Yes
data_source.undeletedTriggered when a data source is restored from the trash. New in 2025-09-03.Yes
comment.createdTriggered when a new comment or suggested edit is added.No
comment.deletedTriggered when a comment is deleted.No
comment.updatedTriggered when a comment is edited.No

Note: More event types may be added in the future. Your subscription won't update automatically to receive new event types, you need to update your subscription in your integration's Webhooks tab.

Webhook payload structure

Notion webhooks use sparse payloads. Rather than including the full content of what changed, each event delivers metadata (the event type, entity ID, timestamp, and contextual information) and expects your integration to call the Notion API to fetch the actual data. This design keeps payloads lightweight and avoids issues with stale data.

Here's an example page.content_updated payload:

{
  "id": "56c3e00c-4f0c-4566-9676-4b058a50a03d",
  "timestamp": "2024-12-05T19:49:36.997Z",
  "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.content_updated",
  "authors": [
    {
      "id": "c7c11cca-1d73-471d-9b6e-bdef51470190",
      "type": "person"
    }
  ],
  "attempt_number": 1,
  "entity": {
    "id": "0ef104cd-477e-80e1-8571-cfd10e92339a",
    "type": "page"
  },
  "data": {
    "updated_blocks": [
      {
        "id": "153104cd-477e-80ec-a87d-f7ff0236d35c",
        "type": "block"
      }
    ],
    "parent": {
      "id": "0ef104cd-477e-80e1-8571-cfd10e92339a",
      "type": "page"
    }
  }
}

Key payload fields

FieldDescription
idUnique ID of the webhook event.
timestampISO 8601 time at which the event occurred. Use this to order events on your side.
workspace_idThe workspace where the event originated.
subscription_idThe ID of the webhook subscription that triggered the delivery.
integration_idThe associated integration ID.
typeThe event type, e.g. page.created, comment.deleted.
authorsArray of objects identifying who performed the action. Each has an id and type (person, bot, or agent). Can be more than one for aggregated events.
accessible_byArray of bot/user objects with access to the entity. Only present for public integrations.
attempt_numberThe current delivery attempt (1–8).
entityThe object that triggered the event, with id and type (page, block, database, data_source, or comment).
dataAdditional event-specific data, such as parent, updated_blocks, or updated_properties.

Security with HMAC signatures

Notion supports HMAC-SHA256 signatures to verify webhook authenticity. When your webhook subscription is created, Notion sends a one-time verification_token to your endpoint. This token serves double duty: it confirms your endpoint is reachable during setup, and it becomes the signing secret for all subsequent webhook deliveries.

Every webhook request from Notion includes an X-Notion-Signature header containing the HMAC-SHA256 hash of the request body, signed with your verification_token.

While payload validation is optional it is strongly recommended for any production environment.

Setting up Notion webhooks

Step 1 — Create a webhook subscription

  1. Visit your integration settings.
  2. Either create a new integration or select an existing one.
  3. Navigate to the Webhooks tab and click + Create a subscription.
  4. Enter your public Webhook URL — it must be a secure (SSL) and publicly accessible endpoint. Localhost endpoints are not reachable.
  5. Choose which event types you'd like to subscribe to. You can modify these later.
  6. Click Create subscription.

At this point, your webhook is created but not yet verified.

Step 2 — Verify the subscription

When you create a subscription, Notion sends a one-time POST request to your webhook URL containing a verification_token:

{
  "verification_token": "secret_tMrlL1qK5vuQAh1b6cZGhFChZTSYJlce98V0pYn7yBl"
}

To complete verification:

  1. Extract the verification_token from the incoming request payload at your endpoint.
  2. Store this token securely — you'll need it for signature validation.
  3. Go back to the Webhooks tab in your integration settings and click Verify.
  4. Paste the verification_token value and click Verify subscription.

Once submitted, your webhook subscription becomes active and will start receiving events.

You can only change the webhook URL before verification. After verification, if you need to change the URL, you must delete and recreate the subscription. You can change the subscribed event types at any time.

Once your subscription is active, implement signature validation for incoming events.

Best practices when working with Notion webhooks

Verifying HMAC signatures

When processing webhooks from Notion, verify the HMAC signature to ensure requests genuinely originated from Notion and haven't been tampered with.

Node.js

import { createHmac, timingSafeEqual } from "crypto";

const VERIFICATION_TOKEN = process.env.NOTION_VERIFICATION_TOKEN;

function verifyNotionSignature(rawBody, signatureHeader) {
  const calculatedSignature = `sha256=${createHmac("sha256", VERIFICATION_TOKEN)
    .update(rawBody)
    .digest("hex")}`;

  return timingSafeEqual(
    Buffer.from(calculatedSignature),
    Buffer.from(signatureHeader)
  );
}

app.post("/webhooks/notion", (req, res) => {
  const signature = req.headers["x-notion-signature"];

  if (!verifyNotionSignature(req.rawBody, signature)) {
    return res.status(401).send("Invalid signature");
  }

  // Process webhook
  res.status(200).send("OK");
});

Python

import hmac
import hashlib
import os
from flask import Flask, request, abort

app = Flask(__name__)

def verify_notion_signature(payload, signature):
    secret = os.environ["NOTION_VERIFICATION_TOKEN"]
    computed = "sha256=" + hmac.new(
        secret.encode("utf-8"),
        payload.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(computed, signature)

@app.route("/webhooks/notion", methods=["POST"])
def handle_notion_webhook():
    signature = request.headers.get("X-Notion-Signature")

    if not verify_notion_signature(request.get_data(as_text=True), signature):
        abort(401)

    # Process webhook
    return "OK", 200

Fetch full content after receiving events

Because Notion uses sparse payloads, your webhook handler should treat each event as a trigger to fetch the latest data from the API. Don't rely on the webhook payload alone for content — use the entity.id to retrieve the full page, database, or comment.

Decouple webhook processing to avoid missed retries

Notion retries failed deliveries up to 8 times over approximately 24 hours. If your endpoint takes too long to respond, Notion may consider the delivery failed. Process webhooks asynchronously to ensure you return a 200 response quickly.

Use the event timestamp for ordering

Notion warns that events may arrive in a different order than they occurred. If event ordering is critical for your workflows, use the event's timestamp field to reorder them rather than relying on delivery order:

async function processEventBatch(events) {
  const sorted = events.sort(
    (a, b) => new Date(a.timestamp) - new Date(b.timestamp)
  );

  for (const event of sorted) {
    await processEvent(event);
  }
}

Implement idempotent processing

Although Notion aims for at-most-once delivery, it's good practice to handle potential duplicates, especially during retries. Use the event id as an idempotency key:

async function processNotionEvent(event) {
  const idempotencyKey = event.id;

  const exists = await redis.get(`processed:${idempotencyKey}`);
  if (exists) {
    console.log(`Event ${idempotencyKey} already processed, skipping`);
    return;
  }

  await handleEvent(event);

  // Mark as processed with a 48-hour TTL
  await redis.setex(`processed:${idempotencyKey}`, 172800, "1");
}

Handle aggregated events correctly

Many Notion events are aggregated — multiple rapid changes are batched into a single webhook delivery. For page.content_updated events, the data.updated_blocks array may contain multiple block IDs. For page.properties_updated, the data.updated_properties array lists the property IDs that changed. Always iterate over these arrays rather than assuming a single change.

Notion webhook limitations and pain points

Sparse payloads require extra API calls

The Problem: Notion webhook payloads contain only event metadata (entity IDs, timestamps, and event types) not the actual content that changed. To get the full page content, property values, or comment text, your integration must make follow-up API calls after every webhook event.

Why It Happens: Notion's sparse payload design keeps webhook deliveries lightweight and avoids sending stale or incomplete data. By the time your endpoint processes the event, the content may have changed again, so Notion expects you to fetch the latest state directly.

Workarounds:

  • Build your webhook handler to immediately queue a fetch for the relevant entity using the Notion API.
  • Cache frequently accessed pages and databases to reduce redundant API calls.
  • Batch multiple events for the same entity before making API calls, since aggregated events may already group rapid changes.

How Hookdeck Can Help: Hookdeck's transformations can enrich sparse payloads by fetching additional data from the Notion API before forwarding the event to your endpoint, reducing the burden on your handler and eliminating the need to build custom enrichment logic.

Event aggregation causes delays and can swallow events

The Problem: Notion batches high-frequency events like page.content_updated within short time windows, delivering them as a single webhook event. While this reduces noise, it introduces a delivery delay (typically under one minute but up to five minutes). Worse, if events like page.created, page.deleted, and page.undeleted occur in quick succession, Notion may only deliver the most meaningful result event (or none at all if the state returns to its original one).

Why It Happens: Event aggregation is a deliberate design choice to reduce redundant webhook deliveries and improve system reliability. It helps prevent webhook storms from rapid edits (like a user typing in a document), but the trade-off is reduced granularity and potential data loss for short-lived state changes.

Workarounds:

  • Don't rely on webhooks for tracking every individual edit — use them as signals that something changed, then fetch the latest state.
  • For use cases requiring detailed change tracking, supplement webhooks with periodic polling as a safety net.
  • Use non-aggregated events like comment.created or page.locked when you need immediate, ungrouped delivery.

How Hookdeck Can Help: Hookdeck can serve as a buffer between Notion and your endpoint, queuing events reliably even if they arrive with delays or in unexpected batches. Hookdeck's event log gives you full visibility into what was delivered and when, making it easy to audit whether events were aggregated or dropped.

No webhook delivery logs or visibility

The Problem: Notion provides no built-in dashboard or logs showing webhook delivery status. You can't see which events were sent, whether they succeeded or failed, what the response codes were, or when retries occurred. The only feedback you get is if a webhook action in an automation fails then you'll see an exclamation mark, and the automation pauses.

Why It Happens: Notion's webhook system is still relatively new, and delivery observability features haven't been built into the platform yet. The integration settings UI shows subscription status but not individual delivery history.

Workarounds:

  • Implement comprehensive logging in your webhook handler, capturing every incoming event with its attempt_number, id, and timestamp.
  • Set up monitoring and alerting on your endpoint to detect delivery gaps.
  • Use a request inspection tool during development to visualize incoming payloads.

How Hookdeck Can Help: Hookdeck's dashboard provides complete visibility into every webhook delivery, including status codes, latency, payloads, headers, and retry attempts. You can search, filter, and inspect events without building any custom logging infrastructure.

No built-in dead letter queue

The Problem: If your endpoint fails to acknowledge an event after all 8 retry attempts (spanning approximately 24 hours), the event is permanently lost. Notion does not maintain a record of failed deliveries for later inspection or replay.

Why It Happens: Notion's at-most-once delivery model prioritizes simplicity over guaranteed delivery. The retry mechanism with exponential backoff handles transient failures, but persistent endpoint issues result in permanent event loss.

Workarounds:

  • Ensure your webhook endpoint has high availability and returns 200 responses quickly.
  • Implement your own logging of received events so you can identify gaps through reconciliation.
  • Use a message queue (like Hookdeck, SQS, RabbitMQ, or Redis) between your endpoint and your processing logic as a buffer.
  • Periodically poll the Notion API as a backup to catch events that may have been missed.

How Hookdeck Can Help: Hookdeck automatically preserves all failed webhooks, allowing you to inspect, debug, and replay them once issues are resolved. This acts as a built-in dead letter queue, ensuring no events are permanently lost due to endpoint downtime.

Webhook URL cannot be changed after verification

The Problem: Once your webhook subscription is verified, you cannot update the endpoint URL. If you need to change the URL (for example when migrating to a new server, changing environments, or updating your routing) you must delete the subscription entirely and recreate it from scratch, including going through verification again.

Why It Happens: The URL is locked after verification as a security measure, tying the verified endpoint to the subscription. However, this creates friction for common operational scenarios.

Workarounds:

  • Plan your URL structure carefully before creating subscriptions.
  • Use a reverse proxy or load balancer URL that you control, so you can change the backend without modifying the webhook URL.
  • Automate subscription teardown and recreation if you need to change URLs frequently.

How Hookdeck Can Help: By pointing your Notion webhook at a stable Hookdeck connection URL, you can change your actual destination endpoint at any time through Hookdeck's dashboard without touching your Notion subscription. This decouples your Notion configuration from your infrastructure routing.

No workspace-level webhook subscriptions

The Problem: Notion webhooks require your integration to have explicit access to each page or database it wants to monitor. You can't set up a single subscription that automatically receives events for everything in a workspace. If a new page is created inside a private page your integration doesn't have access to, the event won't be triggered.

Why It Happens: Notion's permission model is granular so integrations must be explicitly added to pages and databases. This is a security feature, but it means webhook coverage is limited to content your integration has been invited to.

Workarounds:

  • Educate workspace users to add your integration to new pages and databases as they create them.
  • Use the Notion API to periodically search for content your integration has access to and verify coverage.
  • For workspace-wide monitoring, consider using Notion's audit log (available on Enterprise plans) for user and settings changes that webhooks don't cover.

How Hookdeck Can Help: While Hookdeck can't change Notion's permission model, it can help you manage and route events from multiple subscriptions across different pages and databases through a single, organized destination, simplifying the operational overhead of managing many subscriptions.

API rate limits affect follow-up calls

The Problem: Because Notion webhooks use sparse payloads, your integration needs to make follow-up API calls to fetch the full content. Notion's API rate limit is an average of three requests per second per integration. During high-activity periods (when many webhook events arrive in quick succession) your follow-up API calls can easily hit rate limits, resulting in HTTP 429 errors and failed data fetches.

Why It Happens: The sparse payload design combined with the relatively low rate limit creates a bottleneck. Each webhook event typically requires at least one API call (often more) to fetch the data you actually need, and the rate limit doesn't distinguish between webhook-triggered calls and other API usage.

Workarounds:

  • Implement request queuing with rate limiting in your webhook handler.
  • Batch and deduplicate events for the same entity before making API calls.
  • Use exponential backoff when you receive 429 responses.
  • Cache API responses to reduce redundant calls for frequently updated entities.

How Hookdeck Can Help: Hookdeck's rate limiting and queuing capabilities can throttle event delivery to your endpoint, ensuring your handler processes events at a pace that respects Notion's API rate limits. Combined with transformations for payload enrichment, you can reduce the number of follow-up API calls needed.

No support for user or workspace-level events

The Problem: Notion webhooks do not support notifications for user changes (including workspace membership changes, email or name updates, and permission modifications) or workspace and teamspace settings changes. If you need to track who joined the workspace, which permissions changed, or how team settings were modified, webhooks can't help.

Why It Happens: The current webhook system is focused on content events — pages, databases, and comments. User and workspace management events are considered a separate domain and aren't yet part of the webhook event model.

Workarounds:

  • Use Notion's audit log (available on Enterprise plans) for user and workspace changes.
  • Poll the Notion Users API periodically to detect membership changes.
  • Build manual notification workflows for permission changes using Notion's UI automations.

How Hookdeck Can Help: Hookdeck can integrate with multiple event sources. If you build a polling-based system that checks for user or workspace changes, Hookdeck can normalize those polled events into the same webhook pipeline as your real-time Notion events, giving you a unified view across both event types.

Limited to five webhook actions per automation

The Problem: When using Notion's no-code webhook actions (in buttons and database automations), you're limited to a maximum of five webhook actions per automation, and they only support POST requests.

Why It Happens: This is a product limitation of Notion's automation builder, designed for simpler workflows rather than complex multi-destination routing.

Workarounds:

  • Use integration webhooks instead of webhook actions for more flexibility.
  • Build a fan-out service that receives a single webhook and distributes it to multiple destinations.
  • Combine multiple actions into a single endpoint that handles routing internally.

How Hookdeck Can Help: Hookdeck's fan-out capabilities can take a single webhook from Notion and route it to multiple destinations, eliminating the five-action limit. You can also transform the payload differently for each destination.

Testing Notion webhooks

Use the built-in test scenarios

Notion provides three recommended test scenarios after setting up your subscription:

  1. Change a page title — Tests aggregated event delivery. Change the title of a page your integration has access to, wait a minute or two, and verify your endpoint receives a page.content_updated event.
  2. Add a comment — Tests instant event delivery. Add a comment to an accessible page and verify your endpoint receives a comment.created event within seconds. (Requires the "comment read" capability.)
  3. Modify a database schema — Tests structural change events. Add, rename, or delete a property in a connected database and verify your endpoint receives a data_source.schema_updated (or database.schema_updated on older API versions) event.

Use a request inspector

Before building your handler, inspect real Notion payloads using a request inspection service like Hookdeck Console:

  1. Create a temporary inspection URL.
  2. Configure it as your webhook endpoint in your Notion integration.
  3. Trigger real events by making changes in your workspace.
  4. Inspect the payload structure, headers, and timing.

This helps you understand the exact shape of events before writing any processing logic.

Validate in staging

Test your webhook integration with realistic scenarios:

  • Single page creation and deletion.
  • Rapid sequential edits to test aggregation behavior.
  • Multiple property changes on a database page.
  • Comment threads with replies.
  • Schema changes (adding, renaming, and deleting properties).
  • Simulated endpoint downtime to verify retry behavior.

Conclusion

Notion webhooks provide a solid foundation for building real-time integrations with one of the most widely used workspace tools. The event system covers the core content lifecycle (page and database CRUD operations, schema changes, and comments) and the HMAC-SHA256 signature verification adds a strong security layer.

However, the sparse payload design, event aggregation behavior, lack of delivery logs, and absence of a dead letter queue mean production deployments require careful consideration. Implementing proper signature verification, asynchronous processing, and idempotent event handling will address the most common issues.

For integrations with moderate event volumes and reliable endpoints, Notion's built-in webhook system combined with proper error handling works well. For higher-volume use cases, complex multi-destination routing, or mission-critical workflows where delivery guarantees matter, webhook infrastructure like Hookdeck can address Notion's limitations by providing payload enrichment, configurable retries, automatic dead letter queuing, and comprehensive delivery monitoring without modifying your Notion configuration.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.