Notion OAuth: What You Need to Know

If you're building multiple applications or tenants that connect to Notion, this page will save you hours of confusion. Notion's OAuth works differently than other platforms.

The Core Issue

Notion OAuth is user-level, not application-level.

This means:

  • One user = One set of page permissions for your entire integration
  • Connecting multiple tenants doesn't create separate page access
  • Changing page selections affects ALL your connected applications

Real-World Example

Let's say you're building two chatbots for your company (each chatbot is a separate tenant):

Step 1: Connect Sales Chatbot

User: John (Marketing Manager)
Action: Connects Sales Chatbot to Notion
Pages Selected: Marketing Strategy, Sales Playbook, Product Docs
Result: Sales Chatbot can access those 3 pages ✅

Step 2: Connect Support Chatbot

User: John (same person)
Action: Connects Support Chatbot to Notion
Notion Shows: Marketing Strategy ✓, Sales Playbook ✓, Product Docs ✓ (pre-selected)
John Thinks: "I only want Support docs for this chatbot"
John Does: Unchecks Marketing Strategy, Sales Playbook, Product Docs
John Selects: Support FAQ, Help Articles
Result: 
  - Sales Chatbot loses access to Marketing Strategy, Sales Playbook, Product Docs ❌
  - Support Chatbot gets Support FAQ, Help Articles ✅
  - Both chatbots now only have access to Support FAQ, Help Articles

This is the gotcha that confuses everyone.

Why This Happens

When John sees the Notion page picker for the second chatbot, he's not selecting pages "for this chatbot" - he's modifying the global page permissions for the entire integration.

The picker shows the current state of his authorization, not a fresh selection.

The SourceSync Solution

Instead of fighting this behavior, we embrace it. Here's what actually happens with SourceSync:

What SourceSync Does Differently

Traditional approach (causes problems):

Your App: "Sync documents from Notion connection"
SourceSync: Tries to sync → Notion API returns 403 Forbidden → Sync fails silently
Your App: Shows "sync failed" or worse, no indication at all
User: Confused why their content isn't updating

SourceSync's "Reconnect to Sync" approach:

Your App: User clicks "Update Notion Content" 
Your App: Creates new connection → Gets OAuth URL → User goes through Notion picker
User: Sees ALL current pages, consciously selects what they want
Notion: Updates the bot's page permissions to match user's selection
Your App: Calls /ingest/notion API with the connection ID
SourceSync: Successfully syncs the content user explicitly selected

Real Example: How This Solves the Problem

Before (confusing):

Day 1: Sales Chatbot works fine with Marketing pages
Day 5: Support Chatbot gets connected, user unselects Marketing pages  
Day 10: User tries to "resync" Marketing content for Sales Chatbot
Result: Mysterious sync failure, user has no idea why

After (clear):

Day 1: Sales Chatbot works fine with Marketing pages
Day 5: Support Chatbot gets connected, user unselects Marketing pages
Day 10: User clicks "Update Notion Content" for Sales Chatbot
User sees: Only Support pages currently selected in Notion picker
User does: Consciously re-selects Marketing pages they want for Sales Chatbot  
Result: Marketing content syncs successfully, user understands what happened

Why This Works

  • No hidden failures - users always know what pages are selected
  • User is in control - they consciously choose pages during each update
  • Content is safe - existing content in SourceSync never disappears unexpectedly
  • Clear expectations - users understand they're managing global Notion permissions

The Practical Solution: Embrace Shared Access

Here's the reality: Most applications should embrace shared Notion page access and use SourceSync's capabilities to differentiate content per tenant.

How it works:

  • Create separate SourceSync connections for each tenant
  • Accept that they'll share the same Notion page access (this is normal!)
  • Use different tenantIds to automatically separate search results per tenant

Implementation:

// Step 1: Create connection and get OAuth URL
const connectionResponse = await createConnection({
  namespaceId: "ns_123",
  name: "Sales Chatbot",
  tenantId: "sales-bot",
  connector: "NOTION"
})

// Step 2: User goes through OAuth flow at authorizationUrl
// (Connection status becomes ACTIVE after user completes OAuth)

// Step 3: Ingest the Notion content
await fetch('https://api.sourcesync.ai/v1/ingest/notion', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'X-Tenant-Id': 'sales-bot',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    namespaceId: "ns_123",
    ingestConfig: {
      source: "NOTION",
      config: {
        connectionId: connectionResponse.data.connection.id
      }
    }
  })
})

// Step 4: Search the ingested content (automatically scoped by tenantId)
await fetch('https://api.sourcesync.ai/v1/search', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'X-Tenant-Id': 'sales-bot',  // Only gets Sales Chatbot documents
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    query: "product pricing",
    namespaceId: "ns_123"
  })
})

Why this works well:

  • ✅ Simple to implement and maintain
  • ✅ Users typically want similar content across chatbots anyway
  • ✅ Automatic separation by tenantId - no need for complex metadata filtering
  • ✅ Avoids fighting Notion's OAuth design

Common Questions

"Will my content disappear if pages get disconnected?"

No. Your content stays in SourceSync. You just can't sync fresh updates from those pages until you reconnect them.

"Can I have different page access for different chatbots?"

Not with the same user connecting them. But you can embrace shared page access and use different tenantIds to automatically separate search results per chatbot.

"Why doesn't SourceSync prevent this?"

This is how Notion's OAuth works. SourceSync follows OAuth standards and can't override Notion's authorization model.

"Should I create a new connection when I want to update pages?"

Yes, but SourceSync handles deduplication automatically. When you create a new connection for the same tenant and the same user authorizes it, SourceSync detects the matching external identifier (externalId) and reuses the existing connection instead of creating duplicates.

Best Practices

For Your Application

  1. Implement "Reconnect to Sync" UI - provide a way for users to trigger new OAuth flows instead of document-level sync buttons
  2. Set clear user expectations - explain that Notion page access is shared across chatbots in your UI
  3. Use different tenantIds - automatically separates search results per chatbot even with shared Notion access
  4. Plan for shared access - design your chatbots knowing they'll have the same Notion page access

For User Experience

  1. Make reconnection obvious - provide clear "Reconnect Notion" actions rather than hiding sync status
  2. Explain the behavior upfront - your application should educate users about Notion's OAuth behavior
  3. Provide clear reconnection flows - make it easy for users to manage their Notion page selections
  4. Use descriptive connection names - "Sales Bot Notion" not "Connection 1" when calling SourceSync APIs

Technical Implementation

SourceSync Handles

  • ✅ Separate connection records per chatbot
  • ✅ Individual configuration and metadata per connection
  • ✅ Reusing connections when the same tenant reconnects
  • ✅ Tracking external identifiers to understand shared authorization

Your Application Should

  • ✅ Implement "Reconnect Notion" UI flows instead of document-level sync buttons
  • ✅ Use different tenantIds to automatically separate search results per chatbot
  • ✅ Set user expectations about shared Notion page access in your UI
  • ✅ Provide clear reconnection workflows that trigger new SourceSync connection flows

Summary

Notion's OAuth behavior is unique but manageable once you understand it. The key is working with it, not against it:

  • Expect shared page access when the same user connects multiple applications
  • Implement "Reconnect to Sync" UI pattern for updates instead of persistent document-level sync
  • Plan your strategy based on whether shared access works for your use case
  • Set clear expectations with your users about how Notion authorization works

This approach turns what seems like a limitation into a manageable workflow that users can understand and control.