Notion webhook verification, and the one-time token you must not miss

Notion's webhook setup has a step that trips almost everyone: when you create a subscription, Notion sends a single POST to your URL containing a verification_token — and you must read that token out of the request and paste it back into the Webhooks tab of your integration to activate the subscription. If your endpoint wasn't deployed, wasn't logging bodies, or your framework swallowed the request, the token is gone: it is only sent at creation time. That's why "Notion webhook verification" is mostly a question of catching one request.

The setup flow, compressed

  1. In your integration's settings, create a webhook subscription with your endpoint URL.
  2. Notion immediately POSTs {"verification_token": "secret_…"} to that URL — once.
  3. You extract the token and paste it into the Webhooks tab (the ⚙️ verify step). The subscription becomes active.
  4. Keep the token: it's also the HMAC key for every later event.

The zero-code way to catch the token

Point the subscription at a Hookden bin first:

  1. Create a bin (button below, no signup) and use its /h/… URL as the webhook URL.
  2. Create the subscription — the verification_token POST appears in the bin dashboard within a second or two, body stored byte-exact.
  3. Copy the token into the Webhooks tab to activate, and store it as your signing key.

Because the bin answers 200 instantly, the delivery always succeeds — no "endpoint unreachable" dance while your real handler is still half-written. When your handler is ready, either recreate the subscription against your real URL (you'll get a fresh token to catch — this time your handler can log it), or keep the bin in front and relay to localhost while you develop.

What X-Notion-Signature actually is

Every subsequent event carries:

X-Notion-Signature: sha256=461e8cbcba8a75c3edd866f0e71280f5a85cbf21eff040ebd10fe266df38a735

That's sha256= + hex HMAC-SHA256 of the raw request body — exactly GitHub's X-Hub-Signature-256 wire format. The difference is the key: GitHub lets you choose a secret; Notion's key is the verification_token it issued to you. Common verification failures, in the order to check:

  1. Wrong key material. The HMAC key is the verification_token (secret_…) — not your integration's API key ("Internal Integration Secret"). If you stored the wrong one, nothing will ever verify.
  2. Re-serialized body. The signature covers the raw bytes. Parse-then-restringify (hello, every JSON body-parser) changes whitespace and key order and the HMAC fails. Verify against the raw body.
  3. Prefix handling. The header value starts with sha256=; compare against "sha256=" + hexdigest, or strip the prefix first — and compare with a constant-time function.
  4. A stale token. If you delete and recreate the subscription, a new token is issued and old-token verifiers go quiet-invalid.

Two fast ways to check a real delivery: set the bin's signature scheme to Notion and paste the token — every capture gets a ✓/✗ badge; or paste body + header + token into the client-side signature debugger (nothing leaves your browser).

"My events arrive late / out of order / not at all"

Related: GitHub (same signature wire format, different key story), Slack URL verification and Zoom CRC (the other setup-handshake families), and webhooks to localhost for developing your handler behind the bin.

No signup needed. Or from your terminal: curl https://hookden.pages.dev/new

← All guides · Signature debugger · Payload examples · Docs · Hookden vs webhook.site