Channels

A channel integration is the merchant's connection to a delivery provider — Resend for email, Twilio for SMS, the Meta Graph API for Instagram and Facebook posts, and so on. Channels are credentials plus configuration; once a channel is active, marketing campaigns can dispatch through it.

There are two ways to connect:

  1. Static credentials — for providers that authenticate with API keys (Resend, SendGrid, WA Cloud, Twilio, TikTok). POST the credentials directly.
  2. OAuth flow — for providers that require browser hops (Meta, LinkedIn, Twitter, YouTube, Pinterest, Threads). Start at POST /channels/oauth/start and complete via the provider's callback.

This page covers the static-credential surface. The OAuth surface lives at /api/v1/channels/oauth/*; consult the Portal → Channels walkthrough for the dashboard equivalent.

Endpoints

Method Path Purpose
GET /api/v1/channels List channels
POST /api/v1/channels Connect a static-credential channel
GET /api/v1/channels/:id Retrieve a channel
PATCH /api/v1/channels/:id Update a channel
DELETE /api/v1/channels/:id Revoke a channel

All endpoints require the merchant role.

List channels

GET /api/v1/channels

Returns every channel integration in the workspace, newest first. Credentials are never included — the response is the public-safe view.

{
  "data": [
    {
      "id": "chi_01HX...",
      "provider": "email_resend",
      "externalId": null,
      "displayName": "Production transactional",
      "status": "active",
      "config": { "fromEmail": "hello@example.com", "fromName": "ExampleCo" },
      "scopesGranted": [],
      "lastSyncedAt": "2026-05-13T10:42:00.000Z",
      "lastError": null,
      "expiresAt": null,
      "createdAt": "2026-05-01T10:42:00.000Z"
    }
  ],
  "error": null,
  "meta": { "requestId": "...", "timestamp": "..." }
}

Connect a channel

POST /api/v1/channels

For static-credential providers only. The credentials are wrapped using the per-merchant channel encryption key (channel-crypto.ts) before they hit the database, and decrypted only at dispatch time inside the worker.

OAuth-only providers (meta_business, linkedin, twitter, youtube, pinterest, threads) return 400 USE_OAUTH from this endpoint — they require the OAuth flow.

Request body

Field Type Required Notes
provider enum yes See the provider list below.
displayName string (1–120) yes Human label, e.g. "Production transactional". Shown in the dashboard and campaign picker.
externalId string (≤120) | null no Provider-side identifier, e.g. Twilio's messagingServiceSid, WhatsApp's phoneNumberId.
credentials object (string-to-string) yes Raw provider credentials. The exact keys depend on the provider — see Credentials per provider.
config object no Provider-specific configuration (e.g., fromEmail, fromName for email).
scopesGranted string[] no Recorded for audit; not used at runtime.

Response — 201 Created

The full channel object (sans credentials).

Errors

Status error.code When
400 VALIDATION Shape wrong.
400 USE_OAUTH Tried to POST credentials for an OAuth-only provider.

Examples

await ripllo.channels.create({
  provider: 'email_resend',
  displayName: 'Production transactional',
  credentials: { apiKey: process.env.RESEND_API_KEY },
  config: { fromEmail: 'hello@example.com', fromName: 'ExampleCo' },
});

Providers

Category Providers
Email email_resend, email_sendgrid, email_mailgun, email_postmark, email_ses
SMS sms_twilio, sms_vonage
Messaging whatsapp_cloud, whatsapp_twilio, telegram_bot, line_business, discord_webhook, slack_webhook
Push push_onesignal, push_fcm
Social (OAuth only) meta_business, linkedin, tiktok_business, twitter, youtube, pinterest, threads
Generic webhook_generic

tiktok_business straddles the two paths: it accepts static credentials (long-lived access token from the developer portal) but most merchants reach it via OAuth.

Credentials per provider

The exact keys to put in credentials:

Provider Keys
email_resend apiKey
email_sendgrid apiKey
email_mailgun apiKey, domain
email_postmark serverToken
email_ses accessKeyId, secretAccessKey, region
sms_twilio accountSid, authToken
sms_vonage apiKey, apiSecret
whatsapp_cloud accessToken, phoneNumberId (the latter usually also as externalId)
whatsapp_twilio accountSid, authToken, fromNumber (E.164 with whatsapp: prefix, e.g. whatsapp:+14155238886)
telegram_bot botToken
line_business channelAccessToken, channelSecret
discord_webhook webhookUrl
slack_webhook webhookUrl
push_onesignal appId, apiKey
push_fcm serverKey (FCM legacy) or serviceAccountJson (FCM v1)
webhook_generic url, secret (optional, used to sign outbound)

The schema accepts any string-keyed string-valued object — if you pass extra keys, they're stored and ignored. The runtime worker only reads what it needs.

Config per provider

config is opaque to Ripllo's persistence layer; per-provider workers read what they need. Common keys:

  • Email: fromEmail, fromName, replyTo.
  • WhatsApp Cloud: templateNamespace (Meta's template namespace UUID).
  • WhatsApp Twilio: send-time content.contentSid + content.contentVariables map to a pre-approved Twilio Content template; pass content.text instead for freeform replies inside the 24h customer window. Falls back to config.fromNumber if not stored in credentials.
  • Push (OneSignal): appName for routing.

Retrieve a channel

GET /api/v1/channels/:id

Returns one channel by ID, credentials omitted.

Update a channel

PATCH /api/v1/channels/:id

Partial. Mutable fields: displayName, config, scopesGranted, status. To rotate credentials, mint a new channel and revoke the old one — in-place credential edits are not supported because they'd leave the old encrypted blob recoverable via DB snapshots.

Revoke a channel

DELETE /api/v1/channels/:id

Soft-revoke. The channel's status transitions to revoked, and any subsequent campaign send that references this channel returns 400 NO_CHANNEL. The credentials are zeroed (overwritten with the literal string "revoked" rather than null, so DB queries can audit the revoke happened). The row stays for historical reference.

The channel integration object

Field Type Nullable Notes
id string no chi_ + ULID.
accountId string no Owning workspace.
provider enum no See Providers.
externalId string yes Provider-side identifier.
displayName string no Human label.
status enum no active, errored, revoked.
config object no Free-form per-provider config.
credentials (encrypted) no Never returned over the wire.
scopesGranted string[] no Audit-only.
lastSyncedAt, lastError ISO 8601 / string yes Set by the worker on each dispatch.
expiresAt ISO 8601 yes For OAuth tokens with refresh-token expiry.
createdAt, updatedAt ISO 8601 no

OAuth flow (overview)

For OAuth providers, the flow is:

  1. POST /api/v1/channels/oauth/start with { provider, redirectUri }. Returns a provider-specific authorization URL.
  2. The merchant clicks through, authenticates, and is redirected back to your redirectUri with a code + state query string.
  3. POST /api/v1/channels/oauth/callback with { provider, code, state }. Ripllo exchanges the code for tokens, encrypts them, and creates the channel row.

Token refresh is automatic: the worker re-issues refresh tokens when it sees an expiring access token. Errored tokens flip the channel to status: "errored" and surface lastError.

Full OAuth-flow documentation is on the roadmap; the underlying routes are documented inline in channel-oauth.ts.

Events

Event type Fires on Status
ripllo.channel.connected.v1 New channel created (static or OAuth callback). Reserved — not currently emitted.
ripllo.channel.errored.v1 Worker flips a channel to errored after repeated failures. Reserved.
ripllo.channel.revoked.v1 Channel deleted. Reserved.

Next

  • Marketing campaigns — what you do with a connected channel.
  • Contacts — how recipient identifiers map to channels at dispatch time.