Skip to main content
Webhooks expose an HTTP endpoint that external services can call. Use them to push events from external systems into Notion, such as a GitHub push, Stripe event, Zendesk ticket update, or any service that can send an HTTP webhook.

Basic webhook

Define a webhook capability on your worker like this:
After you deploy, Notion creates a URL for each webhook capability. Give that URL to the external service as its webhook destination:

The event object

The execute function receives an array of WebhookEvent objects. The array currently contains one event, but may contain multiple events in the future.
Use the external provider’s own event ID for idempotency when the payload includes one. deliveryId is useful when Notion retries running your worker, but a provider may redeliver the same event as a new HTTP request.

Webhook URLs

Webhook URLs include a unique ID that acts as a shared secret:
Use the CLI to print the URLs for a deployed worker:
For scripts, use JSON or tab-separated output:
Treat webhook URLs as secrets. Anyone with the full URL can send events to the webhook endpoint unless you add provider-specific signature verification inside your worker.

Verify requests in execute

Most webhook providers can sign requests with a shared secret. Store the signing secret as a worker secret, verify each request using event.rawBody and event.headers, and throw WebhookVerificationError when verification fails:
Set the secret before deploying or push it from your local .env file:
See Secrets for more ways to manage worker environment variables.
After 5 consecutive WebhookVerificationError failures, Notion blocks that webhook before running your handler. To reset the failure counter, redeploy the worker or change the synchronous verification setting.
This check runs after Notion has already answered the provider with 202 Accepted, so the provider never sees the result. Use it when all you need is to drop unsigned events. If the provider expects verification in the HTTP response itself, add a verify handler instead.

Verify requests synchronously

Synchronous webhook verification requires @notionhq/workers >= 0.9.0 and ntn CLI version 0.23.1.
Some providers will not accept a webhook until the endpoint answers completes a synchronous challenge flow. You can add a verify handler for these. It runs synchronously before Notion answers the provider, and allows you to control some elements of the webhook HTTP response such as the body and status code. verify runs in the same sandbox as execute, with the full Node.js standard library. Read signing secrets from process.env and use node:crypto for signature checks.

The request object

verify receives the inbound HTTP request, not the array of WebhookEvent objects that execute receives. Like execute, verify receives the capability context as its second argument.

The response object

Choose whether execute runs

Notion always returns your status, body, and content type to the provider. Separately, it decides whether to queue the request for execute. By default, any 2xx status code queues the request for execution, while any non-2xx status does not. You can optionally set a deliver field in your response to control delivery for execution. Setting deliver overrides the status code. Set deliver when the answer to the provider and the delivery decision differ. deliver always wins over the status. A common case is a subscription challenge. The provider needs a 200 with the challenge echoed back, but the challenge isn’t an event, so there’s nothing for execute to do:
Without deliver: false, execute would receive the challenge and would need to skip it. The opposite case is a request you reject but still want to keep. For example, you can answer 401 to a request with a bad signature and still queue it, so execute can log it for auditing:

Timing and failures

verify answers the provider inline, so the run has a wall-clock budget of about 5 seconds that includes starting your worker’s sandbox. Keep it to local computation. Avoid network calls, including context.notion requests, which usually will not fit the budget.
A handler that throws or returns an invalid response counts toward the same five-consecutive-failure limit as WebhookVerificationError, after which Notion blocks the webhook until you redeploy or change the synchronous verification setting. Deliberately returning a 4xx is not a failure and resets the counter.
Exceeding the budget is usually a cold sandbox start. The provider can retry, and will generally land on a warm sandbox.

Turn synchronous verification off

You can turn off a webhook’s verify handler without redeploying the worker. This helps when a verify bug is rejecting real events, or when a webhook is blocked by repeated failures and you need events flowing again right away. Turn it off or back on with the webhook’s key:
The commands use the worker in workers.json. Pass --worker-id to pick a different worker, and --json or --plain for scripted output. You can also use the Sync verification switch in the webhooks table on the worker’s Overview tab. While synchronous verification is off, the webhook acts as if it had no verify handler:
  • POST requests get 202 Accepted and are queued for execute.
  • GET and HEAD requests get 405 Method Not Allowed, so handshake probes fail.
  • execute still runs, so any signature check you do there still applies.
The setting stays in place across deploys. Deploying a new version doesn’t turn synchronous verification back on. Changing the setting in either direction also resets the verification failure counter, which unblocks a blocked webhook.

Execution and retries

When a webhook request reaches Notion, Notion validates the URL, enqueues the event, and responds with 202 Accepted. Your worker runs asynchronously after the HTTP response is sent. A webhook with a verify handler answers with whatever verify returned instead of 202. It enqueues the event when deliver is true, or when deliver is omitted and the status is 2xx. If your handler throws WebhookVerificationError, Notion records a verification failure and does not retry that event. If your handler throws another error, Notion retries the worker run up to 3 times. Successful runs reset the consecutive verification failure counter. Incoming webhook requests can be rejected with 429 before they are queued. A 202 Accepted response means the event was queued for asynchronous processing, not that your handler has run successfully. Queued webhook executions are rate-limited separately and retried when they reach a rate limit. See Limits for the standard thresholds.

Use Notion from a webhook

Webhook handlers receive the same context object as other capabilities, including context.notion, the Notion API SDK client:
For webhooks, context.notion is not automatically authenticated. To call the Notion API, create an internal integration, give it access to the relevant pages or databases, and store the integration token in NOTION_API_TOKEN:
At runtime, context.notion reads process.env.NOTION_API_TOKEN and uses it as the Notion API client token. For more information about creating an integration token for a worker, see Using Notion API from a worker.

Inspect runs

Use worker run logs to debug webhook executions:
To find recent webhook runs quickly:
See the CLI command reference for all ntn workers flags and options.

Next steps

Secrets

Store webhook signing secrets and API keys.

Notion API

Read and write Notion data from a webhook handler.

OAuth

Authenticate with third-party APIs from your webhook.

SDK reference

Detailed API docs for worker.webhook() and WebhookVerificationError.

Limits

Webhook ingress and run rate limits.