service-de-channel-create
**INTERNAL USE ONLY — invoked by `service-de-headless-channel-configure` orchestrator.** Insert an Enhanced messaging `MessagingChannel` record (plus its child `MessagingChannelUsage` as a free server-side side-effect) of any supported type — WhatsApp, LINE, Apple Business Chat, Facebook, or SMS (Te
By forcedotcom · 596 installs
npx skills add forcedotcom/sf-skills --skill service-de-channel-create
Source repository · Upstream listing
Inserting Messaging Channels
Reference File Index
Reference file Load when
references/whatsapp.md MESSAGE TYPE=WhatsApp — WABA partnership (Stage 2) + SF side manage WABA (Stage 3) prereqs, request body, WhatsApp gotchas.
references/line.md MESSAGE TYPE=Line — optional token verify, authDetails body shape, LINE gotchas.
references/apple.md MESSAGE TYPE=AppleBusinessChat — empty config body, Apple precondition errors, Apple gotchas.
references/facebook.md MESSAGE TYPE=Facebook — browser OAuth → page selection flow, Facebook gotchas.
references/sms.md MESSAGE TYPE=Text — phone number platform key, present but unvalidated smsProvider , number provisioning prerequisite (caller's), SMS gotchas.
references/connect insert.md Shared authProviderId lookup + Connect POST + response classification detail (Stage 4).
references/worked examples.md End to end traces per type (fresh insert, preflight short circuit, OAuth).
What this skill does
Given a {MESSAGE TYPE} and its inputs, creates a MessagingChannel record with
PlatformType=Enhanced , IsActive=false . The same server side path
( LiveMessageSetupServiceImpl.addChannel → LiveMessageChannelsUtil.findOrCreateCsotMessagingChannel )
also inserts a MessagingChannelUsage row ( DeploymentType=Conversation , DeploymentStatus=New
on fresh insert — Disabled on a channel that was previously activated then deactivated) — we get
it for free.
One primary path for every type: Connect REST API.
POST /services/data/v{V}/connect/livemessage/channels with an org OAuth session managed by
sf api request rest . Public, sanctioned, live since API v66+. No Aura session, no token extracted
into shell state.
Idempotent on (MessagingPlatformKey, MessageType) — re firing with the same platform key
returns the existing channel's id. This matters because MessagingChannel records are not deletable
via standard means, so the preflight (Stage 1) is the practical idempotency guard.
All types return the same envelope shape , so callers don't branch on type after insert:
{ok, channelId, mcuId, developerName, isActive:false, messageType, messagingPlatformKey, path, created, durationMs} .
Supported types (the only branch point)
{MESSAGE TYPE} Platform key ( messagingPlatformKey ) Prerequisite (caller supplies) Body extras Ref
WhatsApp Phone Number ID service de waba integrate (run in Stage 2 3) externalAccountId , isoCountryCode whatsapp.md
Line LINE channel id LINE id + secret + token (LINE Developers Console) authDetails.{client secret,access token} line.md
AppleBusinessChat Apple BC Account ID (GUID) Apple BC Account ID (register.apple.com/business chat) (none — empty config) apple.md
Facebook Facebook Page ID OAuth handled internally authDetails: {} (OAuth derived) facebook.md
Text Phone number / short code number already provisioned (caller supplies) smsProvider (key required, value may be "" ), isoCountryCode sms.md
Unsupported (would need their own leaf logic, not yet built): WeChat , MsCopilot , Alexa .
For these, emit the unsupported type envelope (Stage 0).
When NOT to use this skill
End to end setup. If the user wants insert → route → activate, invoke
service de headless channel configure — it orchestrates this skill plus routing, consent, and
activation. This skill only handles the insertion step (see Stage 0).
The channel already exists and you only want to re route / activate it. Skip insertion;
call service de channel routing configure / service de channel activate with the existing
channelId .
Inputs (from caller)
{MESSAGE TYPE} — one of WhatsApp , Line , AppleBusinessChat , Facebook , Text .
Case sensitive (matches the SF picklist). Text is the enum value for SMS — there is no SMS type.
{ORG ALIAS} — optional; sf CLI target org alias. Default: sf config get target org .
{API VERSION} — optional; Connect API version. Default: the org's current API version
( sf org display json jq r '.result.apiVersion' ). Always use the org's native version to
avoid 404s.
{CHANNEL NAME} — display name shown in Setup → Messaging Settings. Required for
AppleBusinessChat (no phone/screen name fallback); prompted if omitted for others.
Type specific inputs:
WhatsApp: {WABA ID} (sent as externalAccountId ), {PHONE NUMBER ID} (the platform
key — must differ from WABA ID ), {ISO COUNTRY CODE} (optional, default US ).
Line: {LINE CHANNEL ID} (platform key), {LINE CHANNEL SECRET} , {LINE ACCESS TOKEN} ,
{VERIFY TOKEN FIRST} (optional, default true ).
AppleBusinessChat: {APPLE BC ID} (platform key, GUID).
Facebook: {PAGE ID} (optional — OAuth fetches/prompts if absent).
Text (SMS): {SMS NUMBER} (the platform key — the phone number / short code string, already
provisioned to the org), {SMS PROVIDER} (sent as smsProvider — the smsProvider key must be
present in the body or the insert 400s with "SMS provider is missing", but its value is not
validated or stored , so an empty string "" works; default to "" when the caller doesn't supply
a provider), {ISO COUNTRY CODE} (optional, default US ). This skill does not provision the
number — the caller must already know their number.
Output (to caller)
Success:
created: false + path: "preflight" means the row already existed and was returned idempotently.
Failure (shared):
Type specific failure kinds ( partnership blocked , user declined , waba manage failed ,
meta precondition for WhatsApp; line token invalid for LINE; apple precondition for Apple;
oauth not complete , oauth failed for Facebook) are documented in the per type reference files.
Stage 0: Enforce orchestrator only invocation
This skill is INTERNAL USE ONLY. It should only be invoked by the
service de headless channel configure orchestrator.
If the user's original request was to "install", "set up", "create", or "activate" a messaging
channel (or any phrasing that implies end to end setup), immediately return:
And render to the user:
Only proceed to Stage 0.1 if:
This skill was invoked programmatically by service de headless channel configure
The user explicitly stated they ONLY want insertion (not routing or activation)
This is a retry/resume scenario where routing/activation already succeeded
When in doubt, redirect to the orchestrator.
Stage 0.1: Validate type and required inputs
Look up {MESSAGE TYPE} in the supported types table. If absent, emit the unsupported type
envelope and return.
Validate the type's required inputs are present ( Facebook is the exception — no required
inputs; OAuth → page selection → prompts happen inside the Facebook flow). If any are missing,
emit missing input with the list and return.
WhatsApp only: additionally assert {WABA ID} != {PHONE NUMBER ID} — they are distinct Meta
entities and reusing one value for both causes cryptic activation failures. If equal, emit
{ok:false, kind:"invalid input", hint:"WABA ID and PHONE NUMBER ID must differ — WABA ID is the WhatsApp Business Account ID; PHONE NUMBER ID is a specific phone number within it. Find both in Meta Business Manager → WhatsApp Accounts."} .
Capture {T0} = Date.now() for durationMs .
Stage 0.2: Establish the scratch directory
Every transient artifact this skill writes (preflight query results, request bodies, Connect
responses) goes into a per run scratch directory rooted inside the harness working area — never a
bare absolute /tmp path, which is world writable and non portable. Establish it once:
The base dir is the harness provided ${outputDir} when set, else the private user scoped
${TMPDIR} , else /tmp only as a last resort. All later stages (and the reference flows) write
under ${SCRATCH DIR}/ ; the durable report stays under ${outputDir} .
Stage 1: Preflight — is the channel already here?
SOQL probe for an existing row first ; a hit lets us skip all prerequisites and the insert.
For Facebook without a {PAGE ID} , skip this stage — the page id isn't known until OAuth →
page selection completes (see references/facebook.md ). Run the preflight after page selection.
Two queries, not a subquery. The (SELECT ... FROM MessagingChannelUsages) child subquery
fails with INVALID TYPE on orgs where the child relationship is unnameable (verified on
wadtesting / PRODDEBUG). The FK keyed form works everywhere.
If records.length === 1 :
Record {CHANNEL ID} , {DEVELOPER NAME} , {IS ACTIVE} .
Second query for the MCU by FK:
Record {MCU ID} (empty MCU: flag, don't fail — the activation skill handles it).
Emit the success envelope with path: "preflight", created: false . Return.
If records.length 1 : ambiguous (uniqueness should prevent this) — proceed with the first,
note a warning in the envelope.
If records.length === 0 : proceed to Stage 2.
Stage 2 3: Type specific prerequisites
Most types have no prerequisite here — go straight to Stage 4.
WhatsApp: requires the Meta WABA↔Salesforce partnership (Stage 2, via
service de waba integrate ) and the SF side webhook subscription + credit line share
(Stage 3, POST /connect/livemessage/whatsapp/business account ). Load references/whatsapp.md
and follow it before inserting.
Line: optional token preflight against https://api.line.me/v2/bot/info if
{VERIFY TOKEN FIRST} is true. See references/line.md .
Facebook: browser based OAuth authorization + page list fetch + page selection happen here,
before the insert. Load references/facebook.md and follow it.
Apple: none. (Apple's certificate handshake is provisioned on Apple's side and is per message
at runtime, not at channel insert — see references/apple.md .)
Text (SMS): none at insert. The number must already be provisioned to the org and the caller
must know its smsProvider , but this skill performs no provisioning step — it inserts the record for
a number the caller already has. (ESP registration runs at activation, not insert — see
references/sms.md .)
Stage 4: Insert via Connect REST API
Shared across all types. The only per type difference is the request body ; the authProviderId
lookup, the POST invocation, and the response classification table are identical.
Stage 4.1: authProviderId (org global; required for every type except Text)
One row → {AUTH PROVIDER ID} . Zero rows → branch on type:
Text — non fatal. Text's Connect signature doesn't take authProviderId , so leave
{AUTH PROVIDER ID} unset and continue to Stage 4.2; the POST omits the parameter entirely (see
below). Do not emit the business error for Text — an org that has never run LiveMessage setup
is exactly where the Text only path matters, and blocking here would make the advertised SMS insert
unreachable on those orgs.
every other type ( WhatsApp , Line , AppleBusinessChat , Facebook ) — the org has never run
LiveMessage setup and the insert can't proceed; emit
{ok:false, kind:"business", message:"No AuthProvider 'LiveMessageSetup' found — run the Messaging Setup wizard once on this org first"} .
The Connect endpoint rejects the POST with 400 ILLEGAL QUERY PARAMETER VALUE "Missing argument
authProviderId" if this URL parameter is absent — verified for every type except Text , Apple
included. ( Text has its own Connect signature that doesn't require it.) When the lookup finds a row,
send authProviderId for every type including Text (harmless there); when it finds none, only Text may
proceed, and it proceeds without the parameter. authProviderId goes in the URL query string,
never the body (body → 400 JSON PARSER ERROR "Unrecognized field 'authProviderId'" ).