---
description: Reference for verified Notion webhook ingress from @flue/notion.
title: Notion Channel API | Flue
image: https://flueframework.com/docs/og4.jpg
---

# Notion Channel API

AI-generated, awaiting review [ View as Markdown](https://flueframework.com/docs/api/notion-channel/index.md) 

Import from `@flue/notion`.

## Exports

```
export {
  createNotionChannel,
  type ChannelRoute,
  type JsonValue,
  type NotionChannel,
  type NotionChannelOptions,
  type NotionHandlerResult,
  type NotionKnownWebhookEvent,
  type NotionVerificationHandlerInput,
  type NotionWebhookAccessibleByType,
  type NotionWebhookAuthorType,
  type NotionWebhookEvent,
  type NotionWebhookHandlerInput,
};
```

## `createNotionChannel()`

```
function createNotionChannel<E extends Env = Env>(
  options: NotionChannelOptions<E>,
): NotionChannel<E>;
```

Creates one stateless Notion webhook channel. At least one of`verificationToken` or `verification` is required. `webhook` is always required.

## `NotionChannelOptions`

```
interface NotionChannelOptions<E extends Env = Env> {
  verificationToken?: string;
  bodyLimit?: number;
  verification?(input: NotionVerificationHandlerInput<E>): NotionHandlerResult;
  webhook(input: NotionWebhookHandlerInput<E>): NotionHandlerResult;
}
```

| Field             | Description                                                              |
| ----------------- | ------------------------------------------------------------------------ |
| verificationToken | Token supplied by Notion during endpoint setup and used as the HMAC key. |
| bodyLimit         | Maximum request-body size. Defaults to 1 MiB.                            |
| verification      | Handles the initial unsigned verification-token request.                 |
| webhook           | Receives every verified event after signature verification.              |

The per-subscription signing token already establishes the sending identity through signature verification, so the channel exposes no separate workspace, subscription, or integration constraint options.

The constructor throws `TypeError` for missing handlers, an empty configured token, a non-function callback, or a non-positive body limit.

## Initial verification

```
interface NotionVerificationHandlerInput<E extends Env = Env> {
  c: Context<E>;
  verificationToken: string;
}
```

The initial setup request has no `X-Notion-Signature`. The channel accepts it only when the JSON body is an object containing exactly one non-empty`verification_token` field.

When `verificationToken` is configured, the channel returns an empty `200`only when the received token matches; a mismatch returns `403`, and the temporary `verification` callback is not invoked. Before a token is configured, the `verification` callback receives the setup value and its result becomes the response.

The verification callback is unauthenticated setup code. Store the token outside the channel, then configure it as `verificationToken` for recurring events. Signed events receive `503` while no verification token is configured.

## Recurring events

```
interface NotionWebhookHandlerInput<E extends Env = Env> {
  c: Context<E>;
  event: NotionWebhookEvent;
}
```

Recurring requests require `application/json` and`X-Notion-Signature: sha256=<64 hexadecimal characters>`. The signature is HMAC-SHA256 over the exact request bytes using `verificationToken`. Authentication runs before JSON parsing or `webhook`.

A verified event is forwarded to `webhook` whenever its parsed body is a JSON object carrying a non-empty string `type`; the channel does not re-validate the nested provider shape. Only a signed body that is unparseable JSON, not a JSON object, or missing a string `type` receives `400`. Missing, malformed, or changed signatures receive `401`. Signed events while no verification token is configured receive `503`. Oversized bodies receive `413`; unsupported media types receive `415`.

## Handler result

```
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };

type NotionHandlerResult =
  | undefined
  | JsonValue
  | Response
  | Promise<undefined | JsonValue | Response>;
```

Returning nothing produces an empty `200`. A JSON-compatible value becomes a JSON response. A normal Hono or Fetch `Response` passes through unchanged. Thrown callbacks and unsupported return values produce an empty `500`.

The package does not impose a handler deadline.

## `NotionChannel`

```
interface NotionChannel<E extends Env = Env> {
  readonly routes: readonly ChannelRoute<E>[];
}

interface ChannelRoute<E extends Env = Env> {
  readonly method: string;
  readonly path: string;
  readonly handler: Handler<E>;
}
```

`routes` contains one `POST /webhook` declaration. A file named`channels/notion.ts` is served at `POST /channels/notion/webhook` relative to the `flue()` mount.

Notion has several unrelated resource families, so the package does not expose conversation-key helpers. Applications define the page, database, data source, comment, view, or file identity appropriate to their agent.

## `NotionWebhookEvent`

```
type NotionWebhookEvent = NotionKnownWebhookEvent;
```

`NotionWebhookEvent` is the provider-native webhook payload union, sourced from the webhook payload types exported by the installed official Notion SDK. The only adjustment is widening `authors` to include Notion’s documented `agent`author type; `accessible_by` remains limited to the SDK’s `person | bot`principal types. Field names, nesting, and discriminants are otherwise the provider’s own. `switch (event.type)` narrows each modeled variant. The covered event types are:

* Comments: `comment.created`, `comment.deleted`, `comment.updated`
* Data sources: `data_source.content_updated`, `data_source.created`,`data_source.deleted`, `data_source.moved`, `data_source.schema_updated`,`data_source.undeleted`
* Databases: `database.content_updated`, `database.created`,`database.deleted`, `database.moved`, `database.schema_updated`,`database.undeleted`
* File uploads: `file_upload.completed`, `file_upload.created`,`file_upload.expired`, `file_upload.upload_failed`
* Pages: `page.content_updated`, `page.created`, `page.deleted`,`page.locked`, `page.moved`, `page.properties_updated`,`page.transcription_block.transcript_deleted`, `page.undeleted`,`page.unlocked`
* Views: `view.created`, `view.deleted`, `view.updated`

Known events include `id`, `timestamp`, `workspace_id`, `workspace_name`,`subscription_id`, `integration_id`, `authors`, `attempt_number`,`api_version`, `entity`, and event-specific `data` when the provider supplies it.

```
type NotionWebhookAuthorType = 'person' | 'bot' | 'agent';
type NotionWebhookAccessibleByType = 'person' | 'bot';
```

`@flue/notion` widens the official SDK’s current `authors` declaration to include Notion’s documented `agent` author type. It does not add `agent` to the optional `accessible_by` array, which remains `person | bot`. The channel does not otherwise reshape the provider payload.

## Unmodeled and future events

`NotionWebhookEvent` is a closed union over the event types the installed`@notionhq/client` models. Notion can add event families and API versions, so an authenticated event whose `type` is outside that union is still forwarded to`webhook` with its native, provider-shaped snake-case fields — there is no synthetic `type: 'unknown'` variant, no `eventType`, no `raw`, and no camel-case mirror. At runtime such an event is typed as the union and reached through a`default` arm; inspect `event.type` to handle a family newer than your installed SDK.

```
async webhook({ event }) {
  switch (event.type) {
    case 'page.created':
      // narrowed to the modeled PageCreatedWebhookPayload shape
      return;
    default:
      // any other authenticated event, including types the installed SDK
      // does not yet model, with the provider's native field names
      return;
  }
}
```

Upgrading `@notionhq/client` widens the modeled union, so previously unmodeled types begin narrowing under their own `case`.

## Delivery semantics

The channel exposes Notion’s delivery `id` and `attempt_number` but does not persist deduplication state or restore ordering. Resource and delivery ids are identifiers, not authorization capabilities.

Webhook subscription creation, OAuth, installation and token storage, resource-fetching policy, and outbound API tools remain application concerns.

See [Notion setup](https://flueframework.com/docs/ecosystem/channels/notion/) for initial verification, official client composition, application-owned page identity, and Cloudflare runtime notes.

## Docs Navigation

Current page: [Notion Channel API](https://flueframework.com/docs/api/notion-channel/)

### Sections

* [Guide](https://flueframework.com/docs/getting-started/quickstart/)
* [Reference](https://flueframework.com/docs/api/agent-api/)
* [CLI](https://flueframework.com/docs/cli/overview/)
* [SDK](https://flueframework.com/docs/sdk/overview/)
* [Ecosystem](https://flueframework.com/docs/ecosystem/)

### Runtime

* [ Configuration ](https://flueframework.com/docs/reference/configuration/)
* [ Errors Reference ](https://flueframework.com/docs/api/errors-reference/)
* [ Agent API ](https://flueframework.com/docs/api/agent-api/)
* [ Provider API ](https://flueframework.com/docs/api/provider-api/)
* [ Routing API ](https://flueframework.com/docs/api/routing-api/)
* [ GitHub Channel ](https://flueframework.com/docs/api/github-channel/)
* [ Stripe Channel ](https://flueframework.com/docs/api/stripe-channel/)
* [ Notion Channel ](https://flueframework.com/docs/api/notion-channel/)
* [ Resend Channel ](https://flueframework.com/docs/api/resend-channel/)
* [ Shopify Channel ](https://flueframework.com/docs/api/shopify-channel/)
* [ Intercom Channel ](https://flueframework.com/docs/api/intercom-channel/)
* [ Zendesk Channel ](https://flueframework.com/docs/api/zendesk-channel/)
* [ Salesforce Marketing Cloud Channel ](https://flueframework.com/docs/api/salesforce-marketing-cloud-channel/)
* [ Slack Channel ](https://flueframework.com/docs/api/slack-channel/)
* [ Discord Channel ](https://flueframework.com/docs/api/discord-channel/)
* [ Teams Channel ](https://flueframework.com/docs/api/teams-channel/)
* [ Google Chat Channel ](https://flueframework.com/docs/api/google-chat-channel/)
* [ Linear Channel ](https://flueframework.com/docs/api/linear-channel/)
* [ Telegram Channel ](https://flueframework.com/docs/api/telegram-channel/)
* [ WhatsApp Channel ](https://flueframework.com/docs/api/whatsapp-channel/)
* [ Twilio Channel ](https://flueframework.com/docs/api/twilio-channel/)
* [ Messenger Channel ](https://flueframework.com/docs/api/messenger-channel/)
* [ Streaming Protocol ](https://flueframework.com/docs/api/streaming-protocol/)
* [ Events Reference ](https://flueframework.com/docs/api/events-reference/)

### Advanced

* [ Sandbox Adapter API ](https://flueframework.com/docs/api/sandbox-api/)
* [ Data Persistence API ](https://flueframework.com/docs/api/data-persistence-api/)