Notion Security Basics
Overview
Security fundamentals for the Notion API: integration token management, internal vs public integration models, principle of least privilege for capabilities, page-level access auditing, token rotation, OAuth2 flows for public integrations, and webhook verification. All examples use @notionhq/client v2.x and target the 2022-06-28 API version.
This SKILL.md gives you the workflow at a high level with the essential skeletons inline. Drill into the linked references for the full code:
- Token storage, secret scanning, and access auditing — Step 1 + Step 2 in full
- Token rotation, OAuth2, and webhook verification — Step 3 in full
- Worked examples — dual-integration
.env+ startup validation script
Prerequisites
- Notion integration created at notion.so/my-integrations
- Node.js 18+ with
@notionhq/clientinstalled (npm install @notionhq/client) - Understanding of environment variables and
.envfile patterns - For public integrations: OAuth2 client ID and secret from the integration dashboard
Instructions
Step 1: Secure Token Storage and .env Management
Integration tokens are secrets with the same sensitivity as database passwords. Notion tokens use the ntn_ prefix (current) or secret_ prefix (legacy). Both grant full access to every page shared with the integration. Two rules: never hardcode a token, and gitignore every .env variant BEFORE creating one.
Load from the environment and validate the token format before use:
import { Client } from '@notionhq/client';
const token = process.env.NOTION_TOKEN;
if (!token) throw new Error('NOTION_TOKEN is required (see notion.so/my-integrations).');
if (!token.startsWith('ntn_') && !token.startsWith('secret_')) {
throw new Error('NOTION_TOKEN has an unexpected format (expected ntn_ or legacy secret_).');
}
const notion = new Client({ auth: token });
Add a CI secret-scan step (grep -rE "(ntn_|secret_)[a-zA-Z0-9]{30,}") so an accidentally committed token fails the build. Full .gitignore patterns, the .env.example template, and the complete GitHub Actions workflow are in token storage, secret scanning, and access auditing.
Step 2: Least-Privilege Capabilities and Access Auditing
Configure integration capabilities at the integration dashboard. Each integration should request only the capabilities it actually uses — grant "Read content" to a dashboard, never "Insert/Update"; keep "Read user info (with email)" off unless you truly look up users by email.
Split responsibilities across separate integrations (a read-only acme-reader, a mutating acme-writer) so a leaked reader token cannot write. Audit access with an empty-query notion.search(), which returns every page and database the integration can reach:
const response = await notion.search({ page_size: 100 });
// paginate on response.has_more / response.next_cursor to list all accessible objects
Remember the sharing hierarchy: sharing a parent cascades to children; sharing a child does not expose its parent; and the API returns object_not_found for both missing and unshared pages (intentional, to prevent leakage). The full capability matrix, the dual-client example, and the complete pagination-safe auditIntegrationAccess() function are in token storage, secret scanning, and access auditing.
Step 3: Token Rotation, OAuth2, and Webhook Verification
Rotation (internal integrations): regenerating the secret at the dashboard immediately invalidates the old token, so update your secrets manager (AWS Secrets Manager / GCP Secret Manager / Vault) and restart services FIRST, then verify with curl .../v1/users/me. No separate revocation step is needed.
OAuth2 (public integrations): distribute to other workspaces via the authorization-code flow — redirect to /v1/oauth/authorize with a CSRF state, then exchange the code at /v1/oauth/token using HTTP Basic auth (client_id:client_secret) and store the returned per-workspace access_token encrypted (never in a cookie).
Webhooks: answer the url_verification challenge during setup, validate every payload's shape, respond 200 immediately, and process the event asynchronously.
Full multi-provider rotation commands, the complete Express OAuth2 handlers, and the hardened webhook endpoint are in token rotation, OAuth2, and webhook verification.
Output
After applying this skill:
- Integration tokens stored in environment variables, never in source code
.gitignoreconfigured to exclude all.envvariants- Git secret scanning workflow catches accidental token commits
- Integration capabilities set to the minimum required for each role
- Page access audited — you know exactly which pages the integration can reach
- Token rotation procedure documented with cloud provider commands
- OAuth2 flow implemented for public integrations (if applicable)
- Webhook endpoint validates payloads and responds asynchronously
Error Handling
| Security Issue | Detection | Mitigation |
| ---------------- | ----------- | ------------ |
| Token committed to git | CI secret scan, git log -p -S 'ntn_' | Rotate immediately, rewrite git history with git filter-repo |
| Over-privileged integration | Capability audit at dashboard | Create new integration with minimal capabilities, migrate |
| Stale access to removed pages | Access audit script returns unexpected pages | Revoke page sharing, re-audit |
| Token never rotated | Track created_time of integration | Schedule quarterly rotation, automate with secrets manager |
| OAuth state mismatch | CSRF validation in callback | Reject the request, log the attempt |
| Webhook replay attacks | Duplicate event IDs | Track processed event IDs, skip duplicates |
Examples
The essential skeleton — load a validated token and audit its reach — appears inline in Steps 1 and 2 above. For a complete, copy-ready setup see worked examples, which covers:
- A full
.envfor a dual-integration (reader/writer) architecture with OAuth2 vars - A
validate-notion-config.tsstartup script that fails fast on missing env vars, an invalid/expired token (unauthorized), or an unshared database (object_not_found)
Resources
- Notion API Authorization — token types, OAuth2 flow, scopes
- Create a Notion Integration — capabilities configuration
- API Key Best Practices — storage and rotation
- @notionhq/client npm — official SDK documentation
- Notion API Reference — full endpoint reference
Next Steps
For production deployment checklists, see notion-prod-checklist. For rate limit handling and retry strategies, see notion-rate-limits. For enterprise RBAC patterns with Notion, see notion-enterprise-rbac.