Skip to main content

Firebase Functions Architecture

The Soku backend lives in apps/functions/ and runs as Firebase Cloud Functions on Node.js 22. It handles OAuth for eight social platforms, content publishing and orchestration, a public REST API, webhook processing, media processing, AI features, and scheduled maintenance.

Technology Overview

TechnologyVersionPurpose
Node.js22Runtime
firebase-functions6.xCloud Functions framework
Firebase Admin SDK12.xFirestore, Auth, Storage
Express5.xPublic API HTTP framework
Stripe14.0.0Payment webhook processing
OpenAI SDK4.56.0AI transcription and content
Sharp0.34.5Image processing and thumbnails
FFmpeg (fluent-ffmpeg + ffmpeg-static)2.1 / 5.2Video processing
Pino9.9Structured logging
Zod (via @soku/schema)3.22+Request/response validation
@google-cloud/tasks6.2Cloud Tasks for async publishing
googleapis144.xYouTube Data API
got13.xHTTP client

Directory Structure

apps/functions/
├── index.js # All function exports (entry point)
├── config.js # Firebase Admin init, provider credentials, CORS
├── api.js # Express app for public API
├── ping.js # Health check function
├── adapters/
│ └── gcp/ # GCP-specific adapters (Firestore helpers)
├── http/
│ ├── handlers/ # Standalone HTTPS function handlers
│ │ ├── cancel-scheduled-post.js
│ │ ├── convert-image-to-video.js
│ │ ├── media-proxy.js
│ │ ├── publish-post.js
│ │ ├── render-og-template.js
│ │ ├── report-ai-usage.js
│ │ └── transcribe-media.js
│ └── routes/ # Express route modules for public API
│ ├── ai.js
│ ├── api-keys.js
│ ├── media-upload.js
│ ├── posts.js
│ └── templates.js
├── integrations/ # Per-platform OAuth, publish, poll, metrics
│ ├── facebook/
│ ├── instagram/
│ ├── threads/
│ ├── youtube/
│ ├── tiktok/
│ ├── x/
│ ├── linkedin/
│ ├── snapchat/
│ ├── health.js # Integration health sweep
│ ├── integration-avatar-trigger.js
│ ├── organic-post-trigger.js
│ └── workflow-source-sync.js
├── middleware/ # Express and function middleware
│ ├── auth.js # API key auth, ID token auth, subscription check
│ ├── error-handler.js # Error classes and Express error handler
│ ├── rate-limit.js # Tiered rate limiting with Firestore backend
│ └── validation.js # Request validation middleware
├── orchestrators/ # Publishing orchestration
│ ├── publish-orchestrator.js
│ ├── repost-orchestrator.js
│ ├── clipper-orchestrator.js
│ └── publishing-service.js
├── scheduled/ # Scheduled (cron) functions
│ ├── daily-metrics-snapshot.js
│ └── rate-limit-cleanup.js
├── services/ # Business logic services
│ ├── save-media-buffer.js
│ ├── save-media-from-url.js
│ ├── templates/
│ └── transcriptions.js
├── shared/ # Shared utilities
│ ├── logging.js # Pino logger factory
│ ├── event-logger.js # Tracing and span utilities
│ ├── ai/ # OpenAI integration
│ ├── credits.js # AI credit tracking
│ ├── http.ts # HTTP client helpers
│ ├── ids.ts # ID generation
│ ├── integrations.js # Cross-platform integration helpers
│ ├── integration-schema.js # Integration document schema
│ ├── internal-auth.js # Internal service-to-service auth
│ ├── media/ # Media processing utilities
│ ├── metrics.js # Metrics collection helpers
│ ├── post-doc.js # Post document helpers
│ ├── post-processor.js # Post processing pipeline
│ ├── runs.js # Publishing run tracking
│ ├── targets.js # Platform target resolution
│ ├── token-refresh.js # OAuth token refresh utilities
│ ├── validate-oauth-state.js # OAuth state validation
│ ├── validate-redirect.js # Redirect URL validation
│ ├── validate-url.js # URL validation
│ ├── workflow-account-cleanup.js
│ ├── workflow-dispatcher.js
│ └── workflows.js
├── triggers/ # Firestore document triggers
│ └── library-thumbnail.js
├── types/ # TypeScript type definitions
│ └── types.ts
└── webhooks/ # Webhook handlers
├── meta.js # Meta (Facebook/Instagram) webhooks
└── stripe.js # Stripe webhook handler

Complete Function Exports

Every exported function from index.js is a deployed Cloud Function. They are organized below by category.

Infrastructure

ExportTypeDescription
pingHTTPSHealth check endpoint
apiHTTPS (Express)Public REST API (/v1/*)
mediaHTTPSMedia proxy for serving stored assets

Webhooks

ExportTypeDescription
metaWebhookHTTPSMeta platform webhook verification and event handling
stripeWebhookHTTPSStripe event processing (subscription changes, payments)

Orchestrators

ExportTypeDescription
publishOrchestratorHTTPSReceives publish requests and fans out to per-platform publish functions via Cloud Tasks
repostOrchestratorHTTPSHandles repost/reshare requests across platforms
clipperOrchestratorHTTPSHandles video clipping and cross-platform distribution

Facebook

ExportTypeDescription
fbAuthStartHTTPSInitiates Facebook OAuth flow
fbAuthCallbackHTTPSHandles Facebook OAuth callback
publishFacebookHTTPSPublishes content to Facebook
fbMetricsCollectorHTTPSCollects Facebook post metrics
scheduledFacebookMetricsScheduledPeriodic Facebook metrics collection
onFacebookAccountCreatedFirestore TriggerRuns when a Facebook integration is created

Instagram

ExportTypeDescription
igAuthStartHTTPSInitiates Instagram OAuth flow
igAuthCallbackHTTPSHandles Instagram OAuth callback
publishInstagramHTTPSPublishes content to Instagram
pollInstagramHTTPSOn-demand poll for Instagram publish status
pollerInstagramHTTPSPolling worker for Instagram publish completion
scheduledInstagramPollScheduledPeriodic Instagram publish status polling
onInstagramAccountCreatedFirestore TriggerRuns when an Instagram integration is created
igDataDeletionHTTPSInstagram data deletion callback (GDPR)
igDeauthHTTPSInstagram deauthorization callback

Threads

ExportTypeDescription
thAuthStartHTTPSInitiates Threads OAuth flow
thAuthCallbackHTTPSHandles Threads OAuth callback
publishThreadsHTTPSPublishes content to Threads
thInsightsTestHTTPSThreads insights testing endpoint
thMetricsCollectorHTTPSCollects Threads post metrics
scheduledThreadsMetricsScheduledPeriodic Threads metrics collection

YouTube

ExportTypeDescription
ytAuthStartHTTPSInitiates YouTube OAuth flow
ytAuthCallbackHTTPSHandles YouTube OAuth callback
ytRefreshNowHTTPSOn-demand YouTube token refresh
ytRefreshSweepScheduledPeriodic sweep to refresh expiring YouTube tokens
publishYoutubeHTTPSPublishes content to YouTube
pollYoutubeHTTPSOn-demand poll for YouTube upload status
pollerYoutubeHTTPSPolling worker for YouTube upload completion
scheduledYouTubePollScheduledPeriodic YouTube upload status polling
fetchYouTubeCaptionsHTTPSFetches captions from a YouTube video

TikTok

ExportTypeDescription
ttAuthStartHTTPSInitiates TikTok OAuth flow
ttAuthCallbackHTTPSHandles TikTok OAuth callback
ttRefreshNowHTTPSOn-demand TikTok token refresh
ttRefreshSweepScheduledPeriodic sweep to refresh expiring TikTok tokens
ttCreatorInfoHTTPSFetches TikTok creator info (privacy settings, etc.)
publishTiktokHTTPSPublishes content to TikTok
pollTiktokHTTPSOn-demand poll for TikTok publish status
pollerTiktokHTTPSPolling worker for TikTok publish completion
scheduledTikTokPollScheduledPeriodic TikTok publish status polling
onTiktokAccountCreatedFirestore TriggerRuns when a TikTok integration is created
ttMetricsCollectorHTTPSCollects TikTok post metrics
scheduledTikTokMetricsScheduledPeriodic TikTok metrics collection

X (Twitter)

ExportTypeDescription
xAuthStartHTTPSInitiates X OAuth 2.0 PKCE flow
xAuthCallbackHTTPSHandles X OAuth 2.0 callback
xAuth1StartHTTPSInitiates X OAuth 1.0a flow (for v1.1 media upload)
xAuth1CallbackHTTPSHandles X OAuth 1.0a callback
publishXHTTPSPublishes content to X
pollXHTTPSOn-demand poll for X publish status
pollerXHTTPSPolling worker for X publish completion
scheduledXPollScheduledPeriodic X publish status polling
onXAccountCreatedFirestore TriggerRuns when an X integration is created

LinkedIn

ExportTypeDescription
liAuthStartHTTPSInitiates LinkedIn OAuth flow
liAuthCallbackHTTPSHandles LinkedIn OAuth callback
publishLinkedinHTTPSPublishes content to LinkedIn
onLinkedinAccountCreatedFirestore TriggerRuns when a LinkedIn integration is created

Snapchat

ExportTypeDescription
snapAuthStartHTTPSInitiates Snapchat OAuth flow
snapAuthCallbackHTTPSHandles Snapchat OAuth callback
snapRefreshNowHTTPSOn-demand Snapchat token refresh
snapRefreshSweepScheduledPeriodic sweep to refresh expiring Snapchat tokens
snapPostHTTPSPublishes content to Snapchat

Cross-Platform Triggers

ExportTypeDescription
onOrganicPostCreatedFirestore TriggerFires when a post document is created; handles cross-platform post tracking
onIntegrationAccountAvatarChangeFirestore TriggerFires when an integration account's avatar changes; re-hosts the avatar
onAccountDeletedFirestore TriggerCleans up workflows and data when a user account is deleted
onWorkflowWrittenFirestore TriggerSyncs workflow source account references when a workflow is created or updated
onWorkflowDeletedFirestore TriggerCleans up when a workflow document is deleted
onTemplateWrittenFirestore TriggerProcesses template documents when created or updated
onLibraryAssetUploadedFirestore TriggerGenerates thumbnails when a library asset is uploaded

Standalone HTTP Handlers

ExportTypeDescription
publishPostHTTPSDirect publish endpoint (bypasses orchestrator)
transcribeMediaHTTPSTranscribes audio/video using OpenAI Whisper
reportAiUsageHTTPSReports AI credit consumption
cancelScheduledPostHTTPSCancels a scheduled post
convertImageToVideoHTTPSConverts a static image to a video clip

Scheduled Functions

ExportTypeDescription
integrationsHealthSweepScheduledChecks health of all connected integrations across users
scheduledRateLimitCleanupScheduledPurges stale rate limit documents from Firestore
scheduledDailyMetricsSnapshotScheduledTakes daily snapshots of platform metrics

Express API Structure

The public API is an Express 5 application exported as the api Cloud Function. It is served at /v1/* via Firebase Hosting rewrites.

Middleware Stack (Applied in Order)

  1. Security headers: Sets X-Content-Type-Options, X-Frame-Options, Referrer-Policy, X-DNS-Prefetch-Control, and Strict-Transport-Security.
  2. Body parser: express.json() for JSON request bodies.
  3. CORS: Origin allowlist (mysoku.io, www.mysoku.io, stg.mysoku.io, localhost:3000). Sets Access-Control-Allow-Origin only for allowed origins with Vary: Origin.
  4. Request ID: Attaches a unique request ID to each request for tracing.
  5. Subscription check: Reads the user's subscription status from Firestore and attaches it to req.subscription for downstream tier detection.
  6. Rate limiter: Sliding-window rate limiting backed by Firestore transactions. Tier-based limits: default (25/min), authenticated (60/min), premium (100/min), admin (200/min). Each tier includes a burst allowance. Rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-RateLimit-Tier) are set on every response.

Route Modules

Mount PointModuleDescription
/v1/postsroutes/posts.jsCRUD for posts via the public API
/v1/mediaroutes/media-upload.jsMedia upload endpoints
/v1/api-keysroutes/api-keys.jsAPI key management
/v1/airoutes/ai.jsAI-powered content generation
/v1/templatesroutes/templates.jsTemplate CRUD

A /test endpoint at the root provides a simple health check. Unmatched routes return a 404 via a NotFoundError. The Express error handler is mounted last and formats all errors consistently.

Authentication Middleware

The middleware/auth.js module provides three authentication strategies:

  • authenticateApiKey: Reads the soku-api-key header, SHA-256 hashes it, and looks up the hash in the apiKeys Firestore collection. Rejects if missing, invalid, or revoked.
  • requireIdToken: Reads Authorization: Bearer <token>, verifies the Firebase ID token via Admin SDK, and attaches req.userId.
  • requireActiveSubscription: Checks the user's users/{uid}/subscription/current document for an active or trialing status within the current billing period.

Route modules compose these middleware as needed. Public API routes typically use authenticateApiKey, while user-scoped endpoints use requireIdToken.

OAuth Flow Pattern

All eight platforms follow the same two-function OAuth pattern:

*AuthStart (Initiation)

  1. Receives an HTTPS request with the user's Firebase ID token in the Authorization header.
  2. Verifies the ID token via Admin SDK to get the uid.
  3. Generates a random state string.
  4. Writes an oauthStates/{state} document to Firestore containing { uid, provider, returnTo, createdAt }.
  5. Constructs the platform-specific authorization URL with the appropriate scopes, callback URL, and state parameter.
  6. Returns the authorization URL to the client.

*AuthCallback (Completion)

  1. Receives the OAuth redirect from the platform with code and state query parameters.
  2. Reads and validates the oauthStates/{state} document from Firestore. Deletes it immediately to prevent reuse.
  3. Exchanges the authorization code for access and refresh tokens using the platform's token endpoint.
  4. Fetches the user's profile information from the platform API.
  5. Writes the tokens, profile metadata, and connection status to users/{uid}/integrations/{provider} in Firestore.
  6. Redirects the user back to the returnTo URL (typically the settings page).

Token Refresh

Platforms with expiring tokens (TikTok, YouTube, Snapchat, X) implement two refresh mechanisms:

  • *RefreshNow: On-demand refresh triggered by the client when a token is known to be expired.
  • *RefreshSweep: Scheduled function that queries all integrations for a given platform, identifies tokens expiring soon, and refreshes them proactively.

The shared/token-refresh.js module provides common token refresh logic used across platforms.

X (Twitter) Special Case

X supports two OAuth flows because the platform requires different auth mechanisms for different API versions:

  • OAuth 2.0 PKCE (xAuthStart/xAuthCallback): Used for the v2 API (posting, reading).
  • OAuth 1.0a (xAuth1Start/xAuth1Callback): Used for the v1.1 API (media upload). Requires consumer key and consumer secret in addition to per-user tokens.

Orchestrator Pattern

Orchestrators decouple the "what to publish" decision from the "how to publish" execution. They are HTTPS Cloud Functions that receive a publish request and fan out work to per-platform publish functions.

Publish Flow

Client (web app)


publishOrchestrator (HTTPS, 2GiB memory)

├─ Validates request payload
├─ Resolves target platforms from post configuration
├─ For each platform:
│ └─ Enqueues a Cloud Task targeting the platform's publish function
│ (e.g., publishFacebook, publishInstagram, publishX, etc.)
├─ Writes initial run state to Firestore
└─ Returns immediately with run tracking info


Cloud Tasks execute per-platform publish functions

├─ publishFacebook → Facebook Graph API
├─ publishInstagram → Instagram Content Publishing API
├─ publishThreads → Threads API
├─ publishYoutube → YouTube Data API
├─ publishTiktok → TikTok Content Posting API
├─ publishX → X API v2
├─ publishLinkedin → LinkedIn API
└─ snapPost → Snapchat API


Each publish function updates its run state in Firestore

The publishOrchestrator uses the @google-cloud/tasks library to enqueue Cloud Tasks. Each task carries the post data, target account credentials, and a trace context for distributed tracing. The publishing-service.js module contains the shared orchestration logic.

The repostOrchestrator follows the same pattern but handles resharing existing content to additional platforms. The clipperOrchestrator handles video clipping workflows where a long-form video is cut into clips and distributed.

Event Tracing

Orchestrators use shared/event-logger.js to create distributed traces. A trace is created at the start of the orchestrator and passed to each Cloud Task via the request body. Each per-platform publish function restores the trace context and creates child spans, enabling end-to-end visibility across the async publish pipeline.

Polling Pattern

Some platforms (Instagram, YouTube, TikTok, X) do not provide instant confirmation of publish success. For these, a three-tier polling pattern is used:

  1. poll*: An on-demand HTTPS function the client can call to check the current status of a publish operation.
  2. poller*: A worker function that performs the actual status check against the platform API and updates the run state in Firestore.
  3. scheduled*Poll: A scheduled function that runs periodically to poll all pending publish operations for a given platform.

Scheduled Functions

FunctionDescription
scheduledFacebookMetricsCollects engagement metrics for recent Facebook posts
scheduledInstagramPollPolls pending Instagram publishes for completion
scheduledThreadsMetricsCollects engagement metrics for recent Threads posts
scheduledYouTubePollPolls pending YouTube uploads for processing completion
scheduledTikTokPollPolls pending TikTok publishes for completion
scheduledTikTokMetricsCollects engagement metrics for recent TikTok posts
scheduledXPollPolls pending X posts for completion
ytRefreshSweepRefreshes expiring YouTube tokens
ttRefreshSweepRefreshes expiring TikTok tokens
snapRefreshSweepRefreshes expiring Snapchat tokens
integrationsHealthSweepValidates token health across all connected accounts
scheduledRateLimitCleanupDeletes rate limit documents older than 24 hours
scheduledDailyMetricsSnapshotTakes daily aggregate metrics snapshots

Firestore Triggers

Firestore triggers react to document changes and perform side effects:

FunctionTriggerDescription
onOrganicPostCreatedDocument createTracks organic (non-Soku) posts discovered via platform APIs
onIntegrationAccountAvatarChangeDocument updateRe-hosts integration account avatars to Soku storage
onAccountDeletedDocument deleteCleans up workflows, integrations, and data for deleted accounts
onWorkflowWrittenDocument writeSyncs workflow source account references
onWorkflowDeletedDocument deleteCleans up workflow-related data
onTemplateWrittenDocument writeProcesses and indexes template documents
onLibraryAssetUploadedDocument createGenerates thumbnails for newly uploaded library assets
onFacebookAccountCreatedDocument createPost-connection setup for Facebook accounts
onInstagramAccountCreatedDocument createPost-connection setup for Instagram accounts
onTiktokAccountCreatedDocument createPost-connection setup for TikTok accounts
onXAccountCreatedDocument createPost-connection setup for X accounts
onLinkedinAccountCreatedDocument createPost-connection setup for LinkedIn accounts

Configuration and Initialization

config.js

The configuration module handles three concerns:

  1. Firebase Admin initialization: Initializes the Admin SDK once with the project ID and storage bucket from environment variables. Configures Firestore with ignoreUndefinedProperties: true.

  2. Provider credentials: The cfg() function returns a lazily-initialized configuration object containing OAuth credentials (app IDs, secrets, callback URLs) for all eight platforms plus the OpenAI API key. Credentials come from environment variables.

  3. Configuration validation: Three validation levels based on the environment:

    • None (emulator): No validation, all credentials optional.
    • Basic (development): Warns about missing credentials but continues.
    • Strict (production): Throws if any required credential is missing.
  4. CORS: An origin allowlist (mysoku.io, www.mysoku.io, stg.mysoku.io, localhost:3000) is enforced by both the cors() helper (for individual Cloud Functions) and the corsMiddleware (for the Express app).

Logging

All modules use the shared/logging.js Pino logger factory. Each module creates a named logger via createLogger('module-name') for structured, filterable log output.

Security Model

Public API Authentication

The Express API supports two authentication strategies, applied per-route:

  • API key authentication: The soku-api-key header is SHA-256 hashed and looked up in the apiKeys Firestore collection. The key document contains the owning userId, allowing requests to be scoped to the correct user.
  • Firebase ID token authentication: The Authorization: Bearer <token> header is verified via Admin SDK. Used for user-scoped operations like API key management.

Rate Limiting

Rate limiting uses a sliding-window counter backed by Firestore transactions. Each request's timestamp is added to an array in a rateLimits/{key} document. Timestamps outside the current window are pruned. Four tiers provide escalating limits:

TierMax Requests/minBurst Allowance
Default (unauthenticated)2510
Authenticated (API key)6030
Premium (active subscription)100100
Admin (whitelisted)200500

Rate limit response headers are set on every response. The scheduledRateLimitCleanup function purges stale documents older than 24 hours.

Webhook Verification

  • Stripe: Verifies webhook signatures using STRIPE_WEBHOOK_SECRET. Does not require user authentication.
  • Meta: Handles Facebook/Instagram webhook verification challenges and validates event signatures.
  • Instagram GDPR: igDataDeletion and igDeauth handle Instagram's data deletion and deauthorization callbacks.

OAuth State Security

OAuth state parameters are stored in Firestore with the initiating user's uid. On callback, the state document is validated and immediately deleted to prevent replay attacks. Redirect URLs are validated by shared/validate-redirect.js to prevent open redirect vulnerabilities.

CORS

CORS is restricted to a specific origin allowlist. The Access-Control-Allow-Origin header is only set when the request origin matches an allowed domain. The Vary: Origin header ensures proper caching behavior.

Security Headers

The Express API sets the following security headers on all responses:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin-when-cross-origin
  • X-DNS-Prefetch-Control: off
  • Strict-Transport-Security: max-age=63072000; includeSubDomains