Credential ownership
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
Credential ownership
Which credentials the platform operator holds and which every customer brings themselves — the complete BYOK matrix.
Chatly is multi-tenant. Almost every third-party service is tied to a customer's business, not Chatly the platform. So credentials are BYOK (bring-your-own-key), stored encrypted on the channel or integration row.
You as the platform operator do not buy phone numbers, do not run SMTP, do not own anyone's CRM, and do not hold anyone's WhatsApp Business Account.
What the operator (you) runs
This is the full list. Nothing on it requires a telco, a postal carrier, or a business filing for somebody else's company.
DATABASE_URL # Chatly's Postgres
REDIS_URL # Chatly's Redis
JWT_PRIVATE_KEY_PEM_B64 # Chatly signs its own access tokens
JWT_PUBLIC_KEY_PEM_B64
MFA_ENCRYPTION_KEY # seals every customer's BYOK creds at rest
ADMIN_API_KEY # for the platform admin console
ADMIN_USER_IDS # allowlist for platform-staff impersonation
STRIPE_API_KEY # Chatly charges its OWN customers for Chatly
STRIPE_WEBHOOK_SECRET # (this is your Stripe acct as merchant of record)
S3_* # Optional. Chatly Cloud's shared bucket if you
# run a hosted offering; self-host customers
# bring their own.
OTEL_EXPORTER_OTLP_ENDPOINT # Optional traces/metrics destinationThat's it. There is no TWILIO_*, no OPENAI_API_KEY, no FCM_*, no APNS_*, no POSTMARK_*, no SENDGRID_*, no MAILGUN_* in your env. Those all belong to your customers.
How customers bring their own
Each integration is configured in the dashboard. The dashboard writes the config blob (encrypted) onto the matching channels.config or integrations.config jsonb column. At runtime, the adapter receives this blob and uses it to call the customer's provider.
Channels (per workspace, BYOK)
Channel | What the customer provides |
|---|---|
Web chat | Nothing — generated for them |
Email — outbound | Postmark / SendGrid / Mailgun / SES API key + sending domain (DKIM they set up at their registrar) |
Email — inbound | The customer points their MX record at their existing email provider (Postmark Inbound Streams, SendGrid Inbound Parse, Mailgun Routes, or SES → SNS) and configures that provider to POST to one of our webhook URLs. No SMTP server lives at Chatly. |
SMS / Voice | Twilio Account SID + Auth Token + a phone number they bought from Twilio. Or Telnyx / SignalWire — same idea. Chatly does not own any phone numbers. |
Their Meta Business Account, their WABA phone number, their Cloud API access token | |
Messenger / Instagram | Their Facebook Page + Instagram Business token |
Apple Business Chat | Their Apple Business Register entry + message-service-provider creds |
Telegram | Their bot token |
Discord | Their bot token + Application ID |
LINE / Viber | Their official-account creds |
Outbound services (per workspace, BYOK)
Service | What they provide |
|---|---|
Slack | OAuth install into their Slack workspace |
HubSpot / Salesforce / Shopify / Linear / GitHub | OAuth tokens or PATs against their tenant |
Stripe (their billing for their customers) | Stripe Connect Express onboarding (if using hired-agents) |
OpenAI / Anthropic | API key — counted against THEIR cost ceiling, not yours |
Whisper / Deepgram (STT) | Same as LLMs |
LiveKit (voice / cobrowse) | Cloud users share Chatly's LiveKit project (Chatly Cloud only). Self-host = customer's LiveKit |
APNs / FCM (push to their mobile app) | Their |
SSO / SCIM | Their IdP metadata / discovery URL |
Object storage (their attachments + recordings) | Self-host customers point at their own bucket. Cloud customers get a Chatly-managed prefix. |
What Chatly itself uses (platform-shared)
Stripe — for Chatly's own subscription billing (you charge your customers for Chatly).
Postgres + Redis + S3 — for the platform's data.
OTel collector — optional, for your own observability.
That's the entire platform-owned credential surface.
Why this is the only architecture that works
If we owned Twilio numbers on customers' behalf:
We'd need a separate Twilio sub-account per customer (Twilio doesn't really do this).
We'd be the merchant of record for telco usage and have to bill back.
We'd own all the compliance burden for SMS A2P 10DLC, STIR/SHAKEN, etc.
Customer can't port their existing numbers.
If we ran SMTP:
We'd be on every spam blocklist within a week, dragging down every customer's deliverability.
Inbound TLS / SPF / DKIM / DMARC failures become our problem.
Storage of raw MIME at our level means we're now the data-controller for their inbound email.
So we don't. Every customer plugs their own providers in and Chatly is the orchestration + UI on top.
Implementation note
Channel and integration adapters in code take a config: Record<string, unknown> parameter at the call site. They never read process.env for customer-owned credentials. The orchestration layer (api → worker bus) fetches the workspace's row, decrypts the config jsonb with MFA_ENCRYPTION_KEY, and passes it in.
See:
apps/api/src/modules/channels/adapters/*.ts— channel BYOK shapesapps/api/src/modules/integrations/handlers/*.ts— outbound integration BYOKapps/api/src/modules/channels/email-inbound.controller.ts— inbound webhook receiversapps/voice/src/recording.ts— voice STT + S3 BYOKapps/worker/src/handlers/push-dispatcher.ts— push BYOK per workspace