Integrations Guide

Tours connects to third-party providers for payments, email, storage, analytics, video, and a range of premium services (SMS, WhatsApp, realtime chat, AI chatbot, live chat, CAPTCHA, calendar, social login, and shipping). All integrations are configured in Admin → Integrations (/admin/settings/integrations), and every secret is stored encrypted server-side — credentials are never written to .env and never exposed to the browser.

Each provider ships as a built-in add-on (src/product/addons/<id>/). A small set of providers is free; everything else is a premium add-on unlocked by a license entitlement or a purchase code. This guide covers the channel catalog, how to connect a provider, the free-vs-premium model, and where credentials live. For how these integrations are built, see the Add-on Development Guide.

The channel catalog

Integrations are organized into channels, each holding one or more providers. The full catalog is assembled in src/core/channels/registry.ts (base channels + installed add-ons). Tours ships these providers across channels:

Channel Free providers Premium add-on providers
Payments COD, Bank Transfer, PayPal Stripe, Razorpay, Square, Mollie, Paystack, Flutterwave, PayU, Authorize.Net, Checkout.com, Mercado Pago, Coinbase Commerce, Airwallex, dLocal, eWAY, PayTabs, Tap, Telr, Viva Wallet, Xendit, Yoco
Email SMTP SendGrid, Postmark, Amazon SES
Storage Cloudflare R2 Amazon S3, Google Cloud Storage, DigitalOcean Spaces
Analytics Google Analytics (GA4) Google Tag Manager, Meta Pixel, Microsoft Clarity, PostHog, TikTok
Video YouTube, Vimeo Mux, Cloudflare Stream, Bunny
SMS Twilio, MSG91
WhatsApp WhatsApp Cloud API, Twilio WhatsApp
Realtime Chat Pusher, Ably
AI Chatbot OpenAI, Anthropic, OpenRouter
Live Chat Crisp, Tawk, Intercom, Freshchat, Tidio, Zendesk
CAPTCHA reCAPTCHA v2, reCAPTCHA v3
Calendar Google Calendar, Calendly
Social Login Google, Facebook
Shipping NimbusPost

The base product ships only the channels that contain a free provider (Email, Payments, Storage, Analytics, Video). Entirely-premium channels (SMS, WhatsApp, chat, chatbot, live chat, CAPTCHA, calendar, social, shipping) appear in the catalog once their add-on is bundled.

What each channel powers in the marketplace

Channel Where travelers and operators feel it
Payments Checkout — collecting the booking total (departure price × travelers + extras − coupon) and deposits
Email Booking confirmations, payout notices, password resets, operator invitations
Storage Tour photos and operator media, optimized to WebP before upload
Analytics Traffic and conversion tracking on the public site
Video Tour intro videos embedded on the listing page
SMS / WhatsApp Departure reminders and booking updates
Realtime / Live Chat Traveler↔operator messaging and support widgets
AI Chatbot An assistant that answers tour and booking questions
CAPTCHA Bot protection on enquiry, register, and review forms
Calendar Syncing departures to an operator's calendar
Social Login One-tap traveler sign-in

Free vs premium tiers

The free base set is defined in src/core/channels/tiers.ts:

Code
email:smtp · analytics:ga4 · payments:cod · payments:bank · payments:paypal
storage:r2 · video:youtube · video:vimeo

Every other provider is premium. At runtime premiumBlock(headers, channel, provider) (src/lib/premium.ts) decides access:

  1. Free provider → always allowed.
  2. License entitlement covers it — "*" / "all" (full unlock), the channel id (whole-channel unlock), or the exact "<channel>:<provider>" key → allowed.
  3. Purchase code for that add-on has been activated on this domain → allowed.
  4. Otherwise → blocked with an unlock prompt.

On development / localhost hosts, all providers are unlocked for testing.

Connecting a provider

Admin → Integrations (/admin/settings/integrations)

  1. Find the channel card (e.g. Payments) and pick a provider (e.g. Stripe).
  2. Click Connect and fill in the provider's fields. Field definitions come from the provider's ProviderDef; secret-typed fields are encrypted before storage and shown masked (••••••••) thereafter.
  3. Save. The connection is recorded in the integration_connections table with an is_active flag; you can mark one connection primary per channel (the one the marketplace uses by default).
  4. For a premium provider not covered by your main license, click to enter a purchase code. POST /api/v1/admin/addons/license ({ channel, provider, code }) validates the code and stores the entitlement for your domain; GET /api/v1/admin/addons/license lists active add-on licenses.

A premium integration is live only when it is entitled (license or purchase code) and connected + active.

Terminal
# Verify a channel resolves a primary connection at runtime (dev)
curl -s http://localhost:3000/api/v1/admin/integrations/status \
  -H "Cookie: <admin-session>" | jq '.payments'
# → { "provider": "stripe", "connected": true, "primary": true }

Provider field reference (examples)

The exact fields each provider asks for are declared in its ProviderDef. Two representative examples:

Stripe (Payments — premium)

Field Type Notes
Mode select Live / Sandbox (Test)
Publishable Key text pk_live_… / pk_test_…
Secret Key secret sk_live_… / sk_test_…
Webhook Secret secret Endpoint signing secret

Setup steps (shown in admin): get API keys from the Stripe Dashboard → Developers → API keys, then add a webhook endpoint and copy its signing secret.

PayPal (Payments — free)

Field Type Notes
Mode select Live / Sandbox
Client ID text PayPal Developer → Apps & Credentials
Secret secret App secret
Webhook ID text Optional — for verifying PayPal webhook events

Cloudflare R2 (Storage — free)

Field Type Notes
Account ID text Cloudflare dashboard → R2
Access Key ID text R2 API token key id
Secret Access Key secret R2 API token secret
Bucket text Target bucket name
Public Base URL text CDN / custom domain in front of the bucket

Email, analytics, video, and the premium channels each present their own fields the same way. Whichever storage provider is selected, uploaded images are optimized to WebP before being stored, so tour photos stay light on the public site.

Where credentials live

  • Channel credentials entered in admin are encrypted (AES-256-GCM, key derived from SECRET_KEY) and stored in the database (integration_connections) — there is nothing to add to .env. They are decrypted only server-side when a driver runs, and always masked in API responses.
  • The only baseline secrets in the environment are the platform's own: DATABASE_URL, JWT_SECRET, and SECRET_KEY.
  • Because credentials live in the database, they travel with a DB dump — after restoring a backup, an integration keeps working without re-entering keys (see the Backup and Restore guide).
TS
// How a driver reads its saved config at request time (conceptual)
const conn = await getPrimaryConnection("payments"); // decrypts secret fields
const sk = conn.config.secret_key;                   // in-memory only, never returned

Rotating or fixing a connection

  • Re-enter a provider's fields in Admin → Integrations to rotate a rotated/leaked key — it's config, not code, so no redeploy is needed.
  • An upload failing with SignatureDoesNotMatch means the stored storage secret is wrong or the token was rotated; fix it by re-entering the Storage credentials. The upload route surfaces the underlying storage error (not a generic 500), so this is diagnosable.
  • Deactivating a connection (is_active = false) leaves the credentials in place but takes the provider offline; the marketplace falls back to another active provider in that channel if one exists.
  • Add-on Development Guide — how a channel integration (a provider + its driver) is built and registered.
  • Plugin Development Guide — feature plugins such as Announcements, Support Desk, and an Operator Payout Ledger.
  • Security Guide — the encryption model and secret-handling rules in full.

© CreativeCape Solutions · creative-cape.com · support@creative-cape.com