Notion webhooks: integration events, the verification token and automations
To set up a Notion webhook, create a subscription in your integration's Webhooks tab, then paste back the verification_token Notion POSTs to your URL. A free webhooks.sh bin shows that token in the request body: no signup, 48 hours, 100 requests a day.
Notion has two separate webhook features: integration webhooks, where developers subscribe a public HTTPS URL to page, database and comment events, and the Send webhook action in buttons, database buttons and database automations, which POSTs to any URL you enter. A free bin is the quickest way to test both. It captures the verification_token Notion sends during subscription setup, and shows the exact payload a Notion automation webhook sends, which Notion can't preview in the app.
Two kinds of Notion webhooks
Integration webhooks are for developers building on the Notion API, while the Send webhook action is a no-code automation step on paid plans.
| Feature | Integration webhooks | Send webhook action |
|---|---|---|
| Set up in | Connection settings, Webhooks tab | Buttons, database buttons, database automations |
| Verification | One-time verification_token POST | None |
| Signature | X-Notion-Signature, HMAC-SHA256 | None, add a custom header instead |
| Payload | Event with the entity id, no content | Selected database page properties |
| On failure | Up to 8 retries over about 24 hours | Automation pauses until you resume it |
| Limits | Events you subscribe to | 5 webhook actions per automation, POST only |
Verify a subscription with a free bin
- Create a free bin in your browser at view.webhooks.page or run
curl -X POST https://webhooks.sh/v1/binsin your terminal. - Open your Notion connection settings, go to the
Webhookstab, click+ Create a subscriptionand paste the bin's ingest URL. - Select the event types you want to receive and click
Create subscription. - Open your bin viewer to see Notion's one-time
POSTand copy theverification_tokenfrom the JSON body. - Paste the token into the Notion verification form and click
Verify subscription.
Because the verification_token is sent in the JSON body, it stays readable in your bin viewer; webhooks.sh only redacts request headers with names like token or secret. Keep your secret view URL private, since that token is also the signing key you'll use to verify future events.
Notion locks the subscription URL once you verify it. If you want to keep using this URL past 48 hours, claim the bin before it expires, or plan to delete and recreate the subscription when you switch to your own server.
# 1. Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins
# 2. Use https://<bin>.webhooks.page/notion as the subscription URL.
# The one-time POST from Notion has a body like this:
# {"verification_token": "secret_tMrlL1qK5vuQAh1b6cZGhFChZTSYJlce98V0pYn7yBl"}
What a Notion event looks like
Every Notion API webhook shares the same outer envelope: id, timestamp, workspace_id, subscription_id, integration_id, type, authors, attempt_number (from 1 to 8), entity (containing the id and type of the page or database) and data. Payloads don't include the changed content itself, so your server needs to fetch the latest page or database from the Notion API when an event arrives (Notion's webhook docs cover setup and payloads).
Events fall into three families: page (page.created, page.content_updated, page.properties_updated, page.moved, page.deleted, page.undeleted, page.locked, page.unlocked), database and data_source (created, content_updated, schema_updated, moved, deleted, undeleted for each), and comment (comment.created, comment.updated, comment.deleted, which require the comment read capability on your connection).
Most events arrive within a minute and should arrive within 5 minutes, though delivery order isn't guaranteed so you should sort them by timestamp, and if a page is created and deleted in quick succession Notion may send only the final event or nothing at all. If your endpoint doesn't acknowledge a delivery, Notion retries up to 8 times with exponential backoff, with the final attempt around 24 hours later (see our webhook retries guide for how retry schedules work).
{
"id": "367cba44-b6f3-4c92-81e7-6a2e9659efd4",
"timestamp": "2024-12-05T23:55:34.285Z",
"workspace_id": "13950b26-c203-4f3b-b97d-93ec06319565",
"subscription_id": "29d75c0d-5546-4414-8459-7b7a92f1fc4b",
"integration_id": "0ef2e755-4912-8096-91c1-00376a88a5ca",
"type": "page.created",
"authors": [{ "id": "c7c11cca-1d73-471d-9b6e-bdef51470190", "type": "person" }],
"attempt_number": 1,
"entity": { "id": "153104cd-477e-809d-8dc4-ff2d96ae3090", "type": "page" },
"data": { "parent": { "id": "0ef104cd-477e-80e1-8571-cfd10e92339a", "type": "page" } }
}
Check X-Notion-Signature
Once your subscription is verified, every delivery includes an X-Notion-Signature header containing sha256= followed by an HMAC-SHA256 digest of the request body, keyed with your verification_token. You need to hash the raw request body bytes exactly as they arrived, because parsing and re-serializing the JSON will change the bytes and break the check, and then compare the digests in constant time. If you use Node.js, the official JavaScript SDK (@notionhq/client v5.23.0 and later) includes verifyWebhookSignature to handle this for you.
The X-Notion-Signature header isn't redacted in the bin viewer, so you can copy a real payload and signature from your bin straight into the HMAC signature verifier to check your verification code against a live delivery.
import { createHmac, timingSafeEqual } from "node:crypto";
export function isFromNotion(rawBody, signatureHeader, verificationToken) {
const expected = "sha256=" + createHmac("sha256", verificationToken).update(rawBody).digest("hex");
const got = Buffer.from(signatureHeader ?? "");
const want = Buffer.from(expected);
return got.length === want.length && timingSafeEqual(got, want);
}
Preview the Send webhook automation action
On paid Notion plans, you can add a Send webhook action to a button, database button or database automation to send an HTTP POST to any URL, with optional custom headers and, in database automations, whichever page properties you pick. Notion doesn't give you a way to preview the JSON payload and suggests using a request inspection site instead: paste your bin's ingest URL into the action, trigger the button or automation once, and read the captured JSON in the viewer.
Each automation can have up to five webhook actions, requests are POST only, and database automations send the page properties you select but not the page contents.
Keep two things in mind when testing automations with a free bin:
- Custom headers whose names contain
authorization,cookie,key,tokenorsecretshow as redacted in the bin viewer. - A failed webhook delivery makes Notion show an exclamation mark and pause the automation until you manually resume it, so point the action at your real destination URL before the free bin expires after 48 hours or hits its 100-request daily cap.
Keep the URL or relay to localhost
A free bin expires after 48 hours and accepts up to 100 requests per day. Because Notion won't let you edit a subscription URL after verification, the simplest way to keep testing on the same webhooks.page URL is to claim the bin with a free Google sign-in before the 48 hours are up, which stops the expiration timer and keeps 30 days of rolling request history.
If you want a permanent endpoint on webhooks.host that can stay as your production subscription URL, the Solo plan ($12 a month, details on pricing) gives you named endpoints with 250,000 requests per billing period. A Solo endpoint can automatically forward every Notion event to up to 3 HTTPS URLs, and you can run the wsh CLI (wsh listen <endpoint> --to localhost:3000) to relay live deliveries straight to your local server without deleting and recreating your Notion subscription.
FAQ
How do I verify a Notion webhook subscription?
When you create a subscription, Notion sends a POST request with a verification_token to your webhook URL, which you paste into the Notion verification form before clicking Verify subscription. Pointing the subscription at a free bin lets you copy that token directly from the captured request body in your browser.
Where do I find the Notion verification_token?
It's inside the JSON body of the one-time POST request Notion sends to your URL right after you click Create subscription. If your server wasn't ready or you missed the request, click Resend token in Notion to have it sent again.
Can I change the Notion webhook URL after verifying?
No, Notion locks the webhook URL once the subscription is verified. To use a new URL, you have to delete the subscription and create a new one, though you can edit the subscribed event types on an existing subscription at any time.
Which Notion plan do I need for the Send webhook action?
You need a paid Notion plan to use the Send webhook action. It's available inside buttons, database buttons and database automations.
Why doesn't the Notion webhook include the page content?
Notion integration events only tell you which object changed and what kind of event happened, rather than sending the full content in the payload. When your endpoint receives an event, use the entity ID to fetch the latest page or database content from the Notion API.
Does Notion retry failed webhooks?
Yes, if your endpoint doesn't acknowledge an integration webhook, Notion retries the delivery up to 8 times using exponential backoff. The final retry happens about 24 hours after the original event.
Last updated
Try it in 10 seconds.
Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.