service-de-channel-create

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Inserting Messaging Channels

插入消息渠道

Reference File Index

参考文件索引

Reference fileLoad 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).
参考文件加载时机
references/whatsapp.md
MESSAGE_TYPE=WhatsApp
— WABA合作关系(第2阶段)+ Salesforce侧WABA管理(第3阶段)前置条件、请求体、WhatsApp注意事项。
references/line.md
MESSAGE_TYPE=Line
— 可选令牌验证、
authDetails
请求体结构、LINE注意事项。
references/apple.md
MESSAGE_TYPE=AppleBusinessChat
— 空配置请求体、Apple前置条件错误、Apple注意事项。
references/facebook.md
MESSAGE_TYPE=Facebook
— 浏览器OAuth→页面选择流程、Facebook注意事项。
references/sms.md
MESSAGE_TYPE=Text
— 电话号码平台密钥、已提供但未验证的
smsProvider
、号码预配前置条件(调用方负责)、SMS注意事项。
references/connect-insert.md
通用AuthProviderId查询 + Connect POST + 响应分类细节(第4阶段)。
references/worked-examples.md
各类型的端到端跟踪示例(全新插入、预检短路、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}
.
给定
{MESSAGE_TYPE}
及其输入,将创建一条
PlatformType=Enhanced
IsActive=false
MessagingChannel
记录。同一服务器端路径(
LiveMessageSetupServiceImpl.addChannel
LiveMessageChannelsUtil.findOrCreateCsotMessagingChannel
)还会自动插入一条
MessagingChannelUsage
行(全新插入时
DeploymentType=Conversation
DeploymentStatus=New
;若渠道曾激活后又停用,则
DeploymentStatus=Disabled
)—— 此为附带的免费功能。
所有类型共用一条核心路径:Connect REST API。 通过
sf api request rest
管理的组织OAuth会话,调用
POST /services/data/v{V}/connect/livemessage/channels
。该端点为公开官方认可接口,自API v66+起可用。无需Aura会话,也不会将令牌提取到shell状态中。
基于
(MessagingPlatformKey, MessageType)
实现幂等性
— 使用相同平台密钥重复触发时,将返回现有渠道的ID。这一点至关重要,因为
MessagingChannel
记录无法通过标准方式删除,因此预检(第1阶段)是实际的幂等性保障机制。
所有类型返回相同的响应结构,因此调用方无需在插入后按类型分支处理:
{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 extrasRef
WhatsApp
Phone Number ID
service-de-waba-integrate
(run in Stage 2-3)
externalAccountId
,
isoCountryCode
whatsapp.md
Line
LINE channel idLINE 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 IDOAuth handled internally
authDetails: {}
(OAuth-derived)
facebook.md
Text
Phone number / short codenumber 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).
{MESSAGE_TYPE}
平台密钥(
messagingPlatformKey
)
前置条件(调用方提供)请求体附加内容参考文件
WhatsApp
电话号码ID
service-de-waba-integrate
(第2-3阶段运行)
externalAccountId
,
isoCountryCode
whatsapp.md
Line
LINE渠道IDLINE ID + 密钥 + 令牌(LINE开发者控制台获取)
authDetails.{client_secret,access_token}
line.md
AppleBusinessChat
Apple BC账户ID(GUID格式)Apple BC账户ID(通过register.apple.com/business-chat注册)(无 — 空配置)
apple.md
Facebook
Facebook页面IDOAuth流程内部处理
authDetails: {}
(OAuth生成)
facebook.md
Text
电话号码/短码号码已完成预配(调用方提供)
smsProvider
(必须存在该字段,值可为
""
;若调用方未提供,默认设为
""
)、
isoCountryCode
sms.md
暂不支持的类型(需单独编写分支逻辑,尚未开发):
WeChat
,
MsCopilot
,
Alexa
。 针对这些类型,将返回
unsupported-type
响应结构(第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
    .
  • 端到端设置:如果用户需要完成插入→路由→激活的全流程,请调用
    service-de-headless-channel-configure
    — 它会编排本技能及路由、授权、激活步骤。本技能仅处理插入环节(见第0阶段)。
  • 渠道已存在,仅需重新路由/激活:跳过插入步骤;使用现有
    channelId
    调用
    service-de-channel-routing-configure
    /
    service-de-channel-activate

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.
  • {MESSAGE_TYPE}
    — 可选值为
    WhatsApp
    ,
    Line
    ,
    AppleBusinessChat
    ,
    Facebook
    ,
    Text
    。 区分大小写(与Salesforce选择列表匹配)。
    Text
    是SMS的枚举值 — 不存在
    SMS
    类型。
  • {ORG_ALIAS}
    — 可选;
    sf
    CLI的目标组织别名。默认值:
    sf config get target-org
    的结果。
  • {API_VERSION}
    — 可选;Connect API版本。默认值:组织当前的API版本(
    sf org display --json | jq -r '.result.apiVersion'
    的结果)。请始终使用组织原生版本,避免出现404错误。
  • {CHANNEL_NAME}
    — 在Setup → Messaging Settings中显示的名称。
    AppleBusinessChat
    必填(无电话号码/屏幕名称作为 fallback);其他类型若未提供则会提示用户输入。
类型特定输入参数:
  • WhatsApp
    {WABA_ID}
    (作为
    externalAccountId
    发送)、
    {PHONE_NUMBER_ID}
    (平台密钥 — 必须与WABA_ID不同)、
    {ISO_COUNTRY_CODE}
    (可选,默认值
    US
    )。
  • Line
    {LINE_CHANNEL_ID}
    (平台密钥)、
    {LINE_CHANNEL_SECRET}
    {LINE_ACCESS_TOKEN}
    {VERIFY_TOKEN_FIRST}
    (可选,默认值
    true
    )。
  • AppleBusinessChat
    {APPLE_BC_ID}
    (平台密钥,GUID格式)。
  • Facebook
    {PAGE_ID}
    (可选 — 若未提供,OAuth流程会自动获取或提示用户输入)。
  • Text (SMS)
    {SMS_NUMBER}
    (平台密钥 — 电话号码/短码字符串,已预配到组织)、
    {SMS_PROVIDER}
    (作为
    smsProvider
    发送 — 请求体中必须存在
    smsProvider
    字段,否则插入会返回400错误:"SMS provider is missing",但其值不会被验证或存储,因此空字符串
    ""
    有效;若调用方未提供,默认设为
    ""
    )、
    {ISO_COUNTRY_CODE}
    (可选,默认值
    US
    )。本技能不负责号码预配 — 调用方必须已拥有该号码。

Output (to caller)

输出结果(返回给调用方)

Success:
json
{"ok": true, "channelId": "0Mj...", "mcuId": "0gL...", "developerName": "...",
 "isActive": false, "messageType": "{MESSAGE_TYPE}", "messagingPlatformKey": "...",
 "path": "connect" | "preflight", "created": true | false, "durationMs": 1234}
created: false
+
path: "preflight"
means the row already existed and was returned idempotently.
Failure (shared):
json
{"ok": false, "kind": "wrong-skill", "correctSkill": "service-de-headless-channel-configure", "hint": "..."}
{"ok": false, "kind": "unsupported-type", "supportedTypes": ["WhatsApp","Line","AppleBusinessChat","Facebook","Text"], "hint": "..."}
{"ok": false, "kind": "missing-input", "missing": ["..."], "hint": "caller must provide all required inputs for this type"}
{"ok": false, "kind": "auth",     "hint": "OAuth token invalid / expired — run 'sf org login web'"}
{"ok": false, "kind": "business", "message": "...", "errorCode": "..."}
{"ok": false, "kind": "transport","status": 500, "message": "..."}
{"ok": false, "kind": "verify-failed", "hint": "Connect returned 201 but follow-up SOQL shows no matching channel"}
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.

成功响应:
json
{"ok": true, "channelId": "0Mj...", "mcuId": "0gL...", "developerName": "...",
 "isActive": false, "messageType": "{MESSAGE_TYPE}", "messagingPlatformKey": "...",
 "path": "connect" | "preflight", "created": true | false, "durationMs": 1234}
created: false
+
path: "preflight"
表示该行已存在,通过幂等性返回现有记录。
通用失败响应:
json
{"ok": false, "kind": "wrong-skill", "correctSkill": "service-de-headless-channel-configure", "hint": "..."}
{"ok": false, "kind": "unsupported-type", "supportedTypes": ["WhatsApp","Line","AppleBusinessChat","Facebook","Text"], "hint": "..."}
{"ok": false, "kind": "missing-input", "missing": ["..."], "hint": "caller must provide all required inputs for this type"}
{"ok": false, "kind": "auth",     "hint": "OAuth token invalid / expired — run 'sf org login web'"}
{"ok": false, "kind": "business", "message": "...", "errorCode": "..."}
{"ok": false, "kind": "transport","status": 500, "message": "..."}
{"ok": false, "kind": "verify-failed", "hint": "Connect returned 201 but follow-up SOQL shows no matching channel"}
类型特定的失败类型(WhatsApp的
partnership-blocked
,
user-declined
,
waba-manage-failed
,
meta-precondition
;LINE的
line-token-invalid
;Apple的
apple-precondition
;Facebook的
oauth-not-complete
,
oauth-failed
)记录在对应类型的参考文件中。

Stage 0: Enforce orchestrator-only invocation

第0阶段:仅允许编排器调用

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:
json
{
  "ok": false,
  "kind": "wrong-skill",
  "hint": "This is an internal insertion skill. For end-to-end channel setup (insert → route → activate), invoke 'service-de-headless-channel-configure' instead with the desired MESSAGE_TYPE.",
  "correctSkill": "service-de-headless-channel-configure"
}
And render to the user:
text
Error: Wrong skill invoked.

This skill (service-de-channel-create) only handles the insertion step.
For complete channel setup, use: /service-de-headless-channel-configure

That orchestrator runs: insert → configure routing → activate in one flow.
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.
本技能仅限内部使用,仅应由
service-de-headless-channel-configure
编排器调用。
如果用户的原始请求是“安装”“设置”“创建”或“激活”消息渠道(或任何暗示端到端设置的表述),立即返回:
json
{
  "ok": false,
  "kind": "wrong-skill",
  "hint": "This is an internal insertion skill. For end-to-end channel setup (insert → route → activate), invoke 'service-de-headless-channel-configure' instead with the desired MESSAGE_TYPE.",
  "correctSkill": "service-de-headless-channel-configure"
}
并向用户展示:
text
Error: Wrong skill invoked.

This skill (service-de-channel-create) only handles the insertion step.
For complete channel setup, use: /service-de-headless-channel-configure

That orchestrator runs: insert → configure routing → activate in one flow.
仅在以下情况时进入第0.1阶段:
  • 本技能由
    service-de-headless-channel-configure
    以编程方式调用
  • 用户明确表示仅需要插入操作(不需要路由或激活)
  • 属于重试/恢复场景,且路由/激活已成功完成
如有疑问,直接引导至编排器。

Stage 0.1: Validate type and required inputs

第0.1阶段:验证类型及必填输入参数

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
.
在支持类型表中查找
{MESSAGE_TYPE}
。若不存在,返回
unsupported-type
响应结构并终止流程。
验证该类型的必填输入参数是否齐全(Facebook除外 — 无必填参数;OAuth→页面选择→提示流程在Facebook内部完成)。若有缺失,返回
missing-input
响应结构并列出缺失项,终止流程。
仅WhatsApp: 额外验证
{WABA_ID} != {PHONE_NUMBER_ID}
— 它们是Meta的不同实体,若复用同一值会导致难以排查的激活失败。若两者相等,返回
{ok:false, kind:"invalid-input", hint:"WABA_ID和PHONE_NUMBER_ID必须不同 — WABA_ID是WhatsApp商务账户ID;PHONE_NUMBER_ID是该账户下的特定电话号码。可在Meta商务管理平台→WhatsApp账户中找到两者。"}
记录
{T0} = Date.now()
用于计算
durationMs

Stage 0.2: Establish the scratch directory

第0.2阶段:创建临时目录

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:
bash
SCRATCH_DIR="$(mktemp -d "${outputDir:-${TMPDIR:-/tmp}}/icc.XXXXXX")"
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}
.

本技能生成的所有临时文件(预检查询结果、请求体、Connect响应)都将存储在测试工具工作区内的单次运行临时目录中 — 绝不使用裸绝对路径
/tmp
,该路径为全局可写且不具备可移植性。只需创建一次:
bash
SCRATCH_DIR="$(mktemp -d "${outputDir:-${TMPDIR:-/tmp}}/icc.XXXXXX")"
基础目录优先使用测试工具提供的
${outputDir}
;若未设置,则使用用户私有范围的
${TMPDIR}
;仅当以上两者都不可用时才使用
/tmp
。后续所有阶段(及参考流程)的文件都将写入
${SCRATCH_DIR}/
;持久化报告存储在
${outputDir}
下。

Stage 1: Preflight — is the channel already here?

第1阶段:预检 — 渠道是否已存在?

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.
bash
sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, DeveloperName, MasterLabel, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE MessagingPlatformKey = '{PLATFORM_KEY}' AND MessageType = '{MESSAGE_TYPE}'" \
  --json > "${SCRATCH_DIR}/preflight.json"
If
records.length === 1
:
  • Record
    {CHANNEL_ID}
    ,
    {DEVELOPER_NAME}
    ,
    {IS_ACTIVE}
    .
  • Second query for the MCU by FK:
    bash
    sf data query --target-org '{ORG_ALIAS}' \
      --query "SELECT Id, DeploymentStatus FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" \
      --json > "${SCRATCH_DIR}/preflight-mcu.json"
    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.

首先通过SOQL查询是否存在现有记录;若存在,可跳过所有前置条件和插入步骤。
若Facebook未提供
{PAGE_ID}
,跳过本阶段
— 页面ID需在OAuth→页面选择完成后才能获取(详见
references/facebook.md
)。在页面选择完成后再执行预检。
使用两次查询,而非子查询
(SELECT ... FROM MessagingChannelUsages)
子查询在子关系不可命名的组织中会返回
INVALID_TYPE
错误(已在
wadtesting
/PRODDEBUG环境中验证)。基于外键的查询方式在所有环境中都有效。
bash
sf data query --target-org '{ORG_ALIAS}' \\
  --query "SELECT Id, DeveloperName, MasterLabel, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE MessagingPlatformKey = '{PLATFORM_KEY}' AND MessageType = '{MESSAGE_TYPE}'" \\
  --json > "${SCRATCH_DIR}/preflight.json"
records.length === 1
  • 记录
    {CHANNEL_ID}
    {DEVELOPER_NAME}
    {IS_ACTIVE}
  • 通过外键执行第二次查询获取MCU:
    bash
    sf data query --target-org '{ORG_ALIAS}' \\
      --query "SELECT Id, DeploymentStatus FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" \\
      --json > "${SCRATCH_DIR}/preflight-mcu.json"
    记录
    {MCU_ID}
    (若MCU为空:标记但不终止流程 — 激活技能会处理该情况)。
  • 返回成功响应结构,其中
    path: "preflight", created: false
    。终止流程。
records.length > 1
:结果不明确(唯一性约束应避免此情况) — 取第一条记录,在响应结构中添加警告信息。
records.length === 0
:进入第2阶段。

Stage 2-3: Type-specific prerequisites

第2-3阶段:类型特定前置条件

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
    .)

大多数类型前置条件 — 直接进入第4阶段。
  • WhatsApp:需要Meta WABA与Salesforce的合作关系(第2阶段,通过
    service-de-waba-integrate
    完成),以及Salesforce侧的Webhook订阅+信用额度共享(第3阶段,调用
    POST /connect/livemessage/whatsapp/business-account
    )。插入前请加载
    references/whatsapp.md
    并遵循其指引
  • Line:若
    {VERIFY_TOKEN_FIRST}
    true
    ,可选对
    https://api.line.me/v2/bot/info
    执行令牌预检。详见
    references/line.md
  • Facebook:基于浏览器的OAuth授权+页面列表获取+页面选择在此阶段完成,之后再执行插入。请加载
    references/facebook.md
    并遵循其指引
  • Apple:无前置条件。(Apple的证书握手在Apple侧配置,且在运行时按消息执行,而非在渠道插入时 — 详见
    references/apple.md
    。)
  • Text (SMS):插入时无前置条件。号码必须已预配到组织,且调用方必须知晓其
    smsProvider
    ,但本技能不执行预配步骤 — 仅为调用方已拥有的号码插入记录。(ESP注册在激活时执行,而非插入时 — 详见
    references/sms.md
    。)

Stage 4: Insert via Connect REST API

第4阶段:通过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.
所有类型共用本阶段流程。唯一的类型差异是请求体;AuthProviderId查询、POST调用、响应分类表均相同。

Stage 4.1: authProviderId (org-global; required for every type except Text)

第4.1阶段:authProviderId(组织全局;除Text外所有类型必填)

bash
sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id FROM AuthProvider WHERE DeveloperName = 'LiveMessageSetup'" \
  --json > "${SCRATCH_DIR}/authprovider.json"
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'"
).
bash
sf data query --target-org '{ORG_ALIAS}' \\
  --query "SELECT Id FROM AuthProvider WHERE DeveloperName = 'LiveMessageSetup'" \\
  --json > "${SCRATCH_DIR}/authprovider.json"
若返回1条记录 → 记录
{AUTH_PROVIDER_ID}
。若返回0条记录 → 按类型分支处理
  • Text
    — 非致命错误。Text的Connect签名不需要
    authProviderId
    ,因此留空
    {AUTH_PROVIDER_ID}
    并进入第4.2阶段;POST请求中完全省略该参数(见下文)。请勿为Text返回业务错误 — 从未运行过LiveMessage设置的组织正是Text专属路径的适用场景,若在此处阻断会导致这些组织无法使用宣传的SMS插入功能。
  • 其他所有类型
    WhatsApp
    ,
    Line
    ,
    AppleBusinessChat
    ,
    Facebook
    )— 组织从未运行过LiveMessage设置,无法继续插入;返回
    {ok:false, kind:"business", message:"未找到AuthProvider 'LiveMessageSetup' — 请先在该组织中运行一次消息设置向导"}
若URL参数中缺少
authProviderId
,Connect端点会返回
400 ILLEGAL_QUERY_PARAMETER_VALUE "Missing argument authProviderId"
— 已验证除
Text
外的所有类型(包括Apple)均如此。(
Text
有自己的Connect签名,不需要该参数。)当查询到记录时,为所有类型(包括Text)发送
authProviderId
(对Text无影响);当未查询到记录时,仅Text可继续,且需不带该参数。
authProviderId
需放在URL查询字符串中,绝不能放在请求体中(放在请求体中会返回
400 JSON_PARSER_ERROR "Unrecognized field 'authProviderId'"
)。

Stage 4.2: Build the type-specific body and POST

第4.2阶段:构建类型特定请求体并执行POST

Resolve
{CHANNEL_NAME}
first (prompt if required and absent — see per-type ref). Then build the body from the supported-types table and POST. Write the body to a file (
--body @...
) — LINE tokens are long and can contain shell-hostile characters.
Append the
authProviderId
query parameter only when Stage 4.1 resolved one — otherwise (the Text-with-no-AuthProvider case) POST to the bare endpoint, since an empty
?authProviderId=
would trip the same
ILLEGAL_QUERY_PARAMETER_VALUE
:
bash
URL="/services/data/v{API_VERSION}/connect/livemessage/channels"
[ -n "${AUTH_PROVIDER_ID:-}" ] && URL="${URL}?authProviderId=${AUTH_PROVIDER_ID}"

sf api request rest "${URL}" \
  --method POST --target-org '{ORG_ALIAS}' \
  --header 'Content-Type: application/json' --header 'Accept: application/json' \
  --body @"${SCRATCH_DIR}/body.json" --include > "${SCRATCH_DIR}/connect-response.txt" 2>&1
--include
prints the HTTP status/header block before the body — read the status from there, not a trailing
-w
marker.
sf api request rest
manages OAuth internally.
For the exact per-type body shape and the full response-classification table (including the
DUPLICATE_VALUE
race back to Stage 1, the Facebook
AUTH_PROVIDER_NEEDS_AUTH
401-vs-400 disambiguation, and per-type precondition errors), load
references/connect-insert.md
plus the per-type ref.

首先确定
{CHANNEL_NAME}
(若必填且未提供则提示用户输入 — 详见对应类型的参考文件)。然后根据支持类型表构建请求体并执行POST。将请求体写入文件
--body @...
)— LINE令牌较长,可能包含对shell不友好的字符。
仅当第4.1阶段获取到
authProviderId
时,才在URL中附加该查询参数 — 否则(Text无AuthProvider的情况)直接调用裸端点,因为空的
?authProviderId=
会触发相同的
ILLEGAL_QUERY_PARAMETER_VALUE
错误:
bash
URL="/services/data/v{API_VERSION}/connect/livemessage/channels"
[ -n "${AUTH_PROVIDER_ID:-}" ] && URL="${URL}?authProviderId=${AUTH_PROVIDER_ID}"

sf api request rest "${URL}" \\
  --method POST --target-org '{ORG_ALIAS}' \\
  --header 'Content-Type: application/json' --header 'Accept: application/json' \\
  --body @"${SCRATCH_DIR}/body.json" --include > "${SCRATCH_DIR}/connect-response.txt" 2>&1
--include
会在响应体前打印HTTP状态/头部信息 — 从此处读取状态码,而非使用尾部的
-w
标记。
sf api request rest
会内部管理OAuth。
关于具体的类型特定请求体结构和完整的响应分类表(包括
DUPLICATE_VALUE
错误回退到第1阶段、Facebook
AUTH_PROVIDER_NEEDS_AUTH
的401与400区分、类型特定前置条件错误),请加载
references/connect-insert.md
及对应类型的参考文件。

Stage 5: Verify the insert

第5阶段:验证插入结果

Two queries (same no-subquery reason as Stage 1). Keep to the universal field set —
ExternalAccountId
and
MessagingPlatform
don't exist on all orgs.
bash
sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" --json

sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, DeploymentStatus, DeploymentType FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" --json
Expected: channel
IsActive=false
; exactly one MCU,
DeploymentType=Conversation
,
DeploymentStatus
in
{New, Disabled}
(fresh inserts land in
New
;
Disabled
is a previously-activated-then-deactivated channel — both are valid starting states for activation). Empty MCU query →
{ok:false, kind:"verify-failed", hint:"Connect returned {CHANNEL_ID} but no child MessagingChannelUsage was created"}
.
Trust the DB, not the Connect response body, for
MasterLabel
/
DeveloperName
— the 201 body carries stale input-rep projections (see per-type gotchas).

执行两次查询(原因与第1阶段相同,避免使用子查询)。仅使用通用字段集 —
ExternalAccountId
MessagingPlatform
并非在所有组织中都存在。
bash
sf data query --target-org '{ORG_ALIAS}' \\
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" --json

sf data query --target-org '{ORG_ALIAS}' \\
  --query "SELECT Id, DeploymentStatus, DeploymentType FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" --json
预期结果:渠道
IsActive=false
;存在且仅存在一条MCU,
DeploymentType=Conversation
DeploymentStatus
{New, Disabled}
(全新插入时为
New
;曾激活后停用的渠道为
Disabled
— 两者均为有效的激活起始状态)。若MCU查询为空 → 返回
{ok:false, kind:"verify-failed", hint:"Connect返回了{CHANNEL_ID}但未创建子记录MessagingChannelUsage"}
以数据库结果为准,而非Connect响应体获取
MasterLabel
/
DeveloperName
— 201响应体中的值为过时的输入投影(详见对应类型的注意事项)。

Stage 6: Report to caller

第6阶段:向调用方返回结果

json
{"ok": true, "channelId": "{CHANNEL_ID}", "mcuId": "<from Stage 5>",
 "developerName": "<authoritative, from Stage 5>", "isActive": false,
 "messageType": "{MESSAGE_TYPE}", "messagingPlatformKey": "{PLATFORM_KEY}",
 "path": "connect", "created": true, "durationMs": <Date.now() - T0>}
Rendered (when invoked directly rather than by the orchestrator):
  • Success — MessagingChannel {CHANNEL_ID} ({developerName}) created via {path}. MCU {mcuId} auto-created. IsActive=false — run routing, then consent, then activate next.
  • Info: Channel already exists — {CHANNEL_ID} ({developerName}). Proceed to routing/activation.
  • Error: {kind}: {message or hint}
On success the caller can immediately invoke
service-de-channel-routing-configure
with the returned
channelId
, then
service-de-channel-consent-configure
, then
service-de-channel-activate
. Those three are message-type-agnostic (they operate on
MessagingChannel.Id
+
MessagingChannelUsage.Id
), so there's no downstream dispatch concern.

json
{"ok": true, "channelId": "{CHANNEL_ID}", "mcuId": "<来自第5阶段>",
 "developerName": "<权威值,来自第5阶段>", "isActive": false,
 "messageType": "{MESSAGE_TYPE}", "messagingPlatformKey": "{PLATFORM_KEY}",
 "path": "connect", "created": true, "durationMs": <Date.now() - T0>}
当直接调用本技能(而非由编排器调用)时,展示以下内容:
  • Success — MessagingChannel {CHANNEL_ID} ({developerName}) created via {path}. MCU {mcuId} auto-created. IsActive=false — run routing, then consent, then activate next.
  • Info: Channel already exists — {CHANNEL_ID} ({developerName}). Proceed to routing/activation.
  • Error: {kind}: {message or hint}
成功后,调用方可立即使用返回的
channelId
调用
service-de-channel-routing-configure
,然后调用
service-de-channel-consent-configure
,最后调用
service-de-channel-activate
。这三个技能与消息类型无关(仅基于
MessagingChannel.Id
+
MessagingChannelUsage.Id
操作),因此无需担心下游分支处理问题。

Gotchas (shared)

通用注意事项

  1. Case-sensitive type names.
    "whatsapp"
    "WhatsApp"
    — the SF picklist, SOQL filters, and Connect enum all match exactly. Normalize upstream; don't make Stage 0.1 case-insensitive.
  2. Preflight is the idempotency guard.
    MessagingChannel
    rows aren't deletable via standard means, so re-runs rely on Stage 1 finding the existing row.
  3. Envelope shape is a tight coupling with callers. If a field is renamed here, every caller (especially
    service-de-headless-channel-configure
    ) silently sees the change — break loudly in tests rather than remap field names.
  4. Per-type gotchas live in the reference files. WABA/phone-id confusion, LINE long-lived vs short-lived tokens, Apple's empty-config body + no-provisioning-callout, Facebook OAuth page selection + 401 disambiguation — load the matching
    references/<type>.md
    when debugging that type.
  1. 类型名称区分大小写
    "whatsapp"
    "WhatsApp"
    — Salesforce选择列表、SOQL过滤器、Connect枚举均严格匹配。请在上游完成标准化;不要让第0.1阶段支持大小写不敏感匹配。
  2. 预检是幂等性保障
    MessagingChannel
    记录无法通过标准方式删除,因此重复运行依赖第1阶段找到现有记录。
  3. 响应结构与调用方强耦合:若此处重命名字段,所有调用方(尤其是
    service-de-headless-channel-configure
    )会无声地受到影响 — 请在测试中主动抛出错误,而非映射字段名称。
  4. 类型特定注意事项在参考文件中:WABA/电话号码ID混淆、LINE长效/短效令牌、Apple空配置请求体+无预配调用、Facebook OAuth页面选择+401错误区分 — 调试对应类型时,请加载匹配的
    references/<type>.md
    文件。",