Skip to content

Notion Services

This document describes the Notion integration services in the Yappa Knowledge Hub# Notion Integration Services

Last Updated: 2026-03-09 Version: 2.0 (Consolidated)

The Notion integration is the most complex part of the Yappa Knowledge Hub backend. It ensures that the cloud-based Notion databases and the local SQLite cache remain in sync.

🏗️ Sync Architecture

We follow a "Pull-Push" sync strategy:

  1. Pull: Fetch new or updated items from Notion via NotionSyncService.
  2. Map: Convert complex Notion "Property" JSON into clean PHP entities using NotionPropertyMapper.
  3. Persist: Save the mapped entities to the local SQLite database.
  4. Push: Send local updates (e.g., status changes, AI summaries) back to Notion.

🏛️ Core Services

🏮 NotionClient

A low-level wrapper around the Notion API using Guzzle.

  • Responsibility: Authentication, rate limiting, and raw HTTP requests.
  • Key Methods: queryDatabase(), createPage(), updatePage().

🔄 NotionSyncService

The orchestrator for bulk synchronization.

  • Responsibility: Iterating through all relevant Notion databases and triggering individual entity syncs.
  • Patterns: Uses "Last Edited Time" to only fetch items changed since the last sync cycle.

📘 NotionPropertyMapper

A static utility class that handles the translation between Notion's data model and our Domain model.

  • Responsibility: Extracting values from Notion properties (Title, Select, Relation, Checkbox) and formatting them for PHP.
  • Architecture: Decouples the rest of the application from the specific structure of the Notion API.

🔄 Data Lifecycle

1. Inbound (Notion → local)

Triggered by the app:sync-notion command. It fetches items from the "Knowledge" and "Categories" databases.

2. Outbound (local → Notion)

Triggered when an AI summary is generated or a user edits a summary in Slack. The NotionSyncService::syncToNotion() method takes a local entity and updates the corresponding page in Notion.


Technical Details

  • Namespace: App\Service\Notion
  • Rate Limiting: Notion API limits apply (3 requests per second). The client handles basic re-try logic.

IMPORTANT

Always use the NotionPropertyMapper for extracting data. Never access the raw Notion JSON array directly in your business logic to avoid fragility.

Response DTOs

SyncResponse

Location: backend/src/DTO/Response/Notion/SyncResponse.php

Represents the result of a sync operation.

Properties:

  • created - Number of items created
  • updated - Number of items updated
  • skipped - Number of items skipped
  • failed - Number of items failed
  • details - Array of operation details
  • message - Summary message

Methods:

  • toArray() - Convert to array for JSON response
  • getTotalProcessed() - Get total items processed
  • incrementCreated(), incrementUpdated(), etc. - Update counters

NotionKnowledgeResponse

Location: backend/src/DTO/Response/Notion/NotionKnowledgeResponse.php

Represents a knowledge item from Notion.

Properties:

  • id, notionId, notionUrl
  • title, content
  • tags, targetGroups
  • status, sourceUrl, aiSummary
  • created, lastEdited
  • categoryId

Mappers

NotionMapper

Maps between Notion pages and application DTOs/entities.

Location: backend/src/Mapper/NotionMapper.php

Key Methods:

  • mapNotionPageToResponse(array $page) - Map Notion page to NotionKnowledgeResponse
  • mapNotionPageToArray(array $page) - Map Notion page to array (legacy)
  • mapKnowledgeToNotionProperties(Knowledge $knowledge) - Map entity to Notion properties
  • mapArrayToNotionProperties(array $data) - Map array to Notion properties

NotionPropertyMapper

Static utility class for mapping Notion property types.

Location: backend/src/Service/Notion/NotionPropertyMapper.php

Conversion Methods:

  • title(string $text) - Convert to Notion title
  • richText(string $text) - Convert to Notion rich text
  • multiSelect(array $values) - Convert to Notion multi-select
  • select(string $value) - Convert to Notion select
  • relation(array $pageIds) - Convert to Notion relation
  • status(string $status) - Convert to Notion status
  • url(?string $url) - Convert to Notion URL
  • number($value) - Convert to Notion number
  • checkbox(bool $value) - Convert to Notion checkbox
  • date(string $start, ?string $end) - Convert to Notion date

Extraction Methods:

  • extractTitle(array $property) - Extract title from Notion
  • extractRichText(array $property) - Extract rich text
  • extractMultiSelect(array $property) - Extract multi-select values
  • extractSelect(array $property) - Extract select value
  • extractRelation(array $property) - Extract relation IDs
  • extractStatus(array $property) - Extract status
  • extractUrl(array $property) - Extract URL
  • extractNumber(array $property) - Extract number
  • extractCheckbox(array $property) - Extract checkbox
  • extractDate(array $property) - Extract date

Configuration

Environment Variables

env
NOTION_API_KEY=secret_xxx
NOTION_KNOWLEDGE_DATABASE_ID=xxx
NOTION_CATEGORIES_DATABASE_ID=xxx
NOTION_WEBHOOK_SECRET=xxx
APP_ENV=prod

Service Configuration

Services are configured in config/services.yaml:

yaml
services:
    App\Service\Notion\NotionWebhookService:
        arguments:
            $webhookSecret: '%env(NOTION_WEBHOOK_SECRET)%'
            $appEnv: '%env(APP_ENV)%'

Usage Examples

Webhook Processing

php
$payload = WebhookPayload::fromArray($requestData);

if (!$webhookService->isConfigured()) {
    throw new \Exception('Webhook not configured');
}

if ($webhookService->requiresSignatureVerification()) {
    $isValid = $webhookService->verifySignature($rawPayload, $signature);
    if (!$isValid) {
        throw new \Exception('Invalid signature');
    }
}

$result = $webhookService->processPayload($payload);
if ($result['action_required']) {
    $syncService->syncFromNotion(['force' => true]);
}

Syncing from Notion

php
$request = SyncRequest::fromArray(['force' => true, 'limit' => 100]);
$response = new SyncResponse();

foreach ($pages as $page) {
    $knowledge = $mapper->mapNotionPageToResponse($page);
    // Process knowledge...
    $response->incrementCreated();
}

return $response->toArray();

Validation

php
$validation = $validationService->validateNotionPage($page);
if (!$validation['valid']) {
    throw new ValidationException($validation['errors']);
}

$isValidId = $validationService->validateNotionId($notionId);

Testing

Unit Tests

  • tests/Unit/Service/Notion/NotionWebhookServiceTest.php
  • tests/Unit/Service/Notion/NotionValidationServiceTest.php
  • tests/Unit/Mapper/NotionMapperTest.php

Integration Tests

  • tests/Integration/Notion/NotionSyncTest.php

Run tests:

bash
php bin/phpunit tests/Unit/Service/Notion/
php bin/phpunit tests/Integration/Notion/

Error Handling

All services use proper exception handling and logging:

php
try {
    $result = $syncService->syncFromNotion($options);
} catch (NotionException $e) {
    $logger->error('Notion sync failed', [
        'error' => $e->getMessage(),
        'trace' => $e->getTraceAsString()
    ]);
    throw $e;
}

Security

  • Webhook signature verification using HMAC-SHA256
  • Environment-aware configuration (dev/prod)
  • Safe payload logging (redacts sensitive data)
  • Validation of all incoming data
  • UUID format validation for Notion IDs

Performance

  • Pagination support for large datasets
  • Batch processing with configurable limits
  • Incremental sync based on last_edited_time
  • Skip unchanged items to reduce API calls