Integrations
Stripe, settlement import, outbound webhooks, SMTP, the background worker, and the MCP endpoint.
Payments
Two adapters live in features/integrations/payment: manual and stripe. See Payment providers for setup.
POST /api/payments/webhooks/[providerKey]verifies the Stripe signature, stores aWebhookEvent, and the worker appliescheckout.session.completedevents. The secret is read fromAIRLINE_WEBHOOK_SECRET_<NAME>.POST /api/integrations/settlements/[connectionKey]accepts settlement evidence from an outside ledger with a machine credential (audiencesettlement-import, scopesettlements:write).
Outbound webhooks
The worker posts OutboxEvent records to active WebhookEndpoint records whose eventTypes include the topic. Endpoint hosts must be listed in OUTBOUND_WEBHOOK_HOSTS. Fields on an endpoint: url, eventTypes, status, secret, failureCount, lastAttemptedAt.
Outbox statuses end in dead_lettered after repeated failures. requeueDeadLetteredOutboxEvent (outboxEventId, expectedAttemptCount, reason of 8 to 500 characters, idempotencyKey) puts one back in the queue. The worker posts to ALERT_WEBHOOK_URL when new dead letters appear, at most once a minute.
SMTP (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM) sends password resets and booking access recovery. Without SMTP_HOST and SMTP_FROM, the recovery form is hidden and recoverCustomerBookingAccess answers "Access codes can't be emailed right now. Contact the airline." Recovery is limited to 5 requests per client per hour.
Background worker
npm run worker runs scripts/runtime-worker.ts. Every 30 seconds it expires inventory holds, expires unpaid storefront bookings after their 24-hour window, and prunes rate-limit buckets. It delivers outbox events continuously and writes a heartbeat every 10 seconds under REQUIRED_WORKER_ID or OUTBOX_WORKER_ID.
GET /api/health returns { status: "ok", worker: "fresh" | "stale" | "not_configured" | "unknown" }, or 503 when the database is unreachable or the process is draining. The worker is fresh if its heartbeat is under 60 seconds old.
AI assistant and MCP
The dashboard chat at /api/completion uses OpenRouter (OPENROUTER_API_KEY, OPENROUTER_MODEL, OPENROUTER_MAX_TOKENS). /api/mcp-transport exposes listModels, queryData, searchModels, getFieldsForType, lookupInputType, createData, updateData, deleteData, and modelSpecificSearch, run as the signed-in user. In production it returns 404 unless AI_ASSISTANT_ENABLED=true.
Machine credentials
issueMachineCredential(name, audience, scopes, expiresAt) returns a token once. Only a hash and a preview are stored. machineCredentialSummaries lists them; revokeMachineCredential(credentialId) revokes.