Basic webhook
Define a webhook capability on your worker like this:The event object
Theexecute function receives an array of WebhookEvent objects. The array currently contains one event, but may contain multiple events in the future.
Webhook URLs
Webhook URLs include a unique ID that acts as a shared secret: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 usingevent.rawBody and event.headers, and throw WebhookVerificationError when verification fails:
.env file:
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.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:
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.
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’sverify 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:
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:
POSTrequests get202 Acceptedand are queued forexecute.GETandHEADrequests get405 Method Not Allowed, so handshake probes fail.executestill runs, so any signature check you do there still applies.
Execution and retries
When a webhook request reaches Notion, Notion validates the URL, enqueues the event, and responds with202 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, includingcontext.notion, the Notion API SDK client:
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:
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: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.