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:
- Pull: Fetch new or updated items from Notion via
NotionSyncService. - Map: Convert complex Notion "Property" JSON into clean PHP entities using
NotionPropertyMapper. - Persist: Save the mapped entities to the local SQLite database.
- 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 createdupdated- Number of items updatedskipped- Number of items skippedfailed- Number of items faileddetails- Array of operation detailsmessage- Summary message
Methods:
toArray()- Convert to array for JSON responsegetTotalProcessed()- Get total items processedincrementCreated(),incrementUpdated(), etc. - Update counters
NotionKnowledgeResponse
Location: backend/src/DTO/Response/Notion/NotionKnowledgeResponse.php
Represents a knowledge item from Notion.
Properties:
id,notionId,notionUrltitle,contenttags,targetGroupsstatus,sourceUrl,aiSummarycreated,lastEditedcategoryId
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 NotionKnowledgeResponsemapNotionPageToArray(array $page)- Map Notion page to array (legacy)mapKnowledgeToNotionProperties(Knowledge $knowledge)- Map entity to Notion propertiesmapArrayToNotionProperties(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 titlerichText(string $text)- Convert to Notion rich textmultiSelect(array $values)- Convert to Notion multi-selectselect(string $value)- Convert to Notion selectrelation(array $pageIds)- Convert to Notion relationstatus(string $status)- Convert to Notion statusurl(?string $url)- Convert to Notion URLnumber($value)- Convert to Notion numbercheckbox(bool $value)- Convert to Notion checkboxdate(string $start, ?string $end)- Convert to Notion date
Extraction Methods:
extractTitle(array $property)- Extract title from NotionextractRichText(array $property)- Extract rich textextractMultiSelect(array $property)- Extract multi-select valuesextractSelect(array $property)- Extract select valueextractRelation(array $property)- Extract relation IDsextractStatus(array $property)- Extract statusextractUrl(array $property)- Extract URLextractNumber(array $property)- Extract numberextractCheckbox(array $property)- Extract checkboxextractDate(array $property)- Extract date
Configuration
Environment Variables
NOTION_API_KEY=secret_xxx
NOTION_KNOWLEDGE_DATABASE_ID=xxx
NOTION_CATEGORIES_DATABASE_ID=xxx
NOTION_WEBHOOK_SECRET=xxx
APP_ENV=prodService Configuration
Services are configured in config/services.yaml:
services:
App\Service\Notion\NotionWebhookService:
arguments:
$webhookSecret: '%env(NOTION_WEBHOOK_SECRET)%'
$appEnv: '%env(APP_ENV)%'Usage Examples
Webhook Processing
$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
$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
$validation = $validationService->validateNotionPage($page);
if (!$validation['valid']) {
throw new ValidationException($validation['errors']);
}
$isValidId = $validationService->validateNotionId($notionId);Testing
Unit Tests
tests/Unit/Service/Notion/NotionWebhookServiceTest.phptests/Unit/Service/Notion/NotionValidationServiceTest.phptests/Unit/Mapper/NotionMapperTest.php
Integration Tests
tests/Integration/Notion/NotionSyncTest.php
Run tests:
php bin/phpunit tests/Unit/Service/Notion/
php bin/phpunit tests/Integration/Notion/Error Handling
All services use proper exception handling and logging:
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