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 |
| 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 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 |
| 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:
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:
- Free provider → always allowed.
- License entitlement covers it —
"*"/"all"(full unlock), the channel id (whole-channel unlock), or the exact"<channel>:<provider>"key → allowed. - Purchase code for that add-on has been activated on this domain → allowed.
- Otherwise → blocked with an unlock prompt.
On development / localhost hosts, all providers are unlocked for testing.
Connecting a provider
Admin → Integrations (/admin/settings/integrations)
- Find the channel card (e.g. Payments) and pick a provider (e.g. Stripe).
- 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. - Save. The connection is recorded in the
integration_connectionstable with anis_activeflag; you can mark one connection primary per channel (the one the marketplace uses by default). - 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/licenselists active add-on licenses.
A premium integration is live only when it is entitled (license or purchase code) and connected + active.
# 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, andSECRET_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).
// 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
SignatureDoesNotMatchmeans 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.
Related guides
- 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