Service De Channel Activate
Activate an Enhanced `MessagingChannel` (WhatsApp/Apple/Facebook/SMS/RCS) by PATCHing `MessagingChannelUsage.DeploymentStatus` from `Disabled` to `Provisioning` via the REST sobject endpoint. The UDD save-hook dispatches by `MessageType` for the external callout (`WHATS_APP`→Meta `/register`, `FACEBOOK`→`subscribeFacebookPage`, `TEXT`→`registerCsotSms`; Apple/LINE need none), writes the terminal `Active`/`Error`, and flips `IsActive`. Synchronous on the HTTP response — WhatsApp returns 204 after Meta confirms (~15-21s); no-callout channels are near-instant. No Aura RPC, no CSRF cookies. Use to activate an already-inserted Enhanced channel headlessly via REST. Do not use for full end-to-end setup — use `service-de-headless-channel-configure`.
- Skill ID
- forcedotcom/sf-skills/service-de-channel-activate
- Publisher
- forcedotcom
- Repository
- sf-skills
- Installs
- 0
- Files
- 4
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
forcedotcom/sf-skills/service-de-channel-activateInstalls these files- SKILL.md
- references/gotchas.md
- references/phone-verification.md
- references/worked-examples.md
What this skill tells the agent
Activating a Messaging Channel
What this skill does
Given a {CHANNEL_ID}, reads the channel's MessagingChannelUsage.Id, then fires PATCH /services/data/v{V}/sobjects/MessagingChannelUsage/{MCU_ID} with body {"DeploymentStatus":"Provisioning"}. The server-side chain:
MessagingChannelUsageFunctions.validateBeforeSaverunsvalidateDeploymentStatus→validateChannelReadinessOnProvisioning. For WhatsApp this confirms consent is configured. Rejected writes return HTTP 400FIELD_INTEGRITY_EXCEPTION.MessagingChannelUsageFunctions.saveHook_PostStmtExecuteOncefires unconditionally after the UPDATE statement. It callsMessagingChannelUsageFunctionsHelper.handlePostSavewhich registers a post-commitTransactionObserver.- At commit, the observer calls
ConversationChannelUsageDeploymentStatusService.handleProvisioning(inherited fromAbstractChannelUsageDeploymentStatusService), which:
- Calls
runProvisioning—switch-dispatches byMessageTypefor the external callout: WHATS_APP→registerCsotWhatsAppNumber→LiveMessageSetupApi.registerWhatsAppNumber→ Meta/register+ status verification. 15-21s wall-clock.FACEBOOK→metaGraphApiService.subscribeFacebookPage.TEXT→registerCsotSms.AppleBusinessChat,Line, everything else →defaultbranch, no external callout, no network wait.- On success: writes
DeploymentStatus = 'Active'via PLSQL. - On failure: writes
DeploymentStatus = 'Error'plusErrorReason/ErrorDetails.
- Inside the same observer, a second pass syncs
MessagingChannel.IsActive = trueonce MCU reachesActive(theisTransitioningStatusflag skips the flip while status is stillProvisioning).
All synchronous within the PATCH request — the 204 response only comes back after the full chain completes. WhatsApp: ~15-21s (Meta /register round-trip). Apple / Line: ~1s (no external call; just the local save-hook + observer + PLSQL write). Verified on wadtesting 2026-04-30.
Reference File Index
| Reference file | Load when |
|---|---|
references/phone-verification.md | Stage 3 comes back with ErrorReason === "VERIFICATION_REQUIRED" (WhatsApp only) — the phone-number OTP verification sub-flow. |
references/worked-examples.md | You want a reference run of the WhatsApp happy-path, Apple activation, a readiness failure, or the already-active no-op. |
references/gotchas.md | Troubleshooting an unexpected result, or before modifying this skill — the eleven known gotchas. |
Why REST PATCH instead of Apex?
A direct REST PATCH produces the identical save-hook chain as the old activateChannelUsage Apex method, with substantially less machinery — no CSRF cookie acquisition, no bootstrap fetch, no Aura response parsing, no double-wrapped returnValue. REST semantics are honest: 204 means the transition succeeded; 4xx means it didn't.
Code proof: MessagingChannelUsageFunctions.saveHook_PostStmtExecuteOnce fires on any DML path (REST, SOAP, Apex, Metadata API) — there is no Apex-specific gate. The entity XML (MessagingChannelUsage.entity.xml) marks DeploymentStatus as editAccess="always" with no <readonly> attribute. The transition validator (getValidAPIStatusTransitions) allows Disabled → Provisioning (and New → Provisioning, and Error → Provisioning | Deprovisioning, and Active → Deprovisioning). The DB-only transitions Provisioning → Active | Error are reserved for the observer's PLSQL call — that's why we write Provisioning and let the server pick the terminal state.
When NOT to use this skill
- The channel is already `IsActive=true`. Re-firing is blocked by the API transition validator (
Active → Provisioningis not ingetValidAPIStatusTransitions()) — the PATCH would return 400. The Stage 1 precondition check catches this and emitsnoop:true. - Routing isn't configured.
activateChannelUsageused to fail withLiveMessageSetupException/nullQueueIdat the Apex entry point. With the REST path the same guard lives invalidateChannelReadinessOnProvisioning— write withSessionHandlerId=null && FallbackQueueId=null→ 400FIELD_INTEGRITY_EXCEPTION. Runservice-de-channel-routing-configurefirst. The Stage 1 check still runs defensively. - The MCU doesn't exist. Can't PATCH a row that's missing. Run the insertion skill first — it always creates the MCU as a side-effect of
addChannel.
Inputs (from caller)
{CHANNEL_ID}— a 15- or 18-charMessagingChannel.Id(prefix0Mj). The channel must already exist with a non-nullSessionHandlerIdorFallbackQueueId.{ORG_ALIAS}— optional; thesfCLI target-org alias. Default: whateversf config get target-orgreturns. Used for OAuth and SOQL reads.{API_VERSION}— optional; REST API version. Default:68.0. Any version whereMessagingChannelUsageis addressable as a standard sobject is fine (v50+ should work; not exhaustively tested).
Unlike the old Aura-based version of this skill, there are no {POLL_TIMEOUT_S} / {POLL_INTERVAL_S} inputs — the PATCH is synchronous end-to-end.
Output (to caller)
Success — channel is live:
{"ok": true, "channelId": "0Mj...", "mcuId": "0gL...", "deploymentStatus": "Active", "isActive": true, "messageType": "WhatsApp", "durationMs": 20934}Success — no-op (already active):
{"ok": true, "noop": true, "channelId": "0Mj...", "message": "Channel already active"}Failure — precondition not met:
{"ok": false, "kind": "no-routing", "hint": "run service-de-channel-routing-configure first"}
{"ok": false, "kind": "no-mcu", "hint": "no MessagingChannelUsage row for this channel — run the insertion skill first"}
{"ok": false, "kind": "channel-missing","hint": "MessagingChannel id not found"}Failure — validator or server-side provisioning error:
{"ok": false, "kind": "readiness-failed", "errorCode": "FIELD_INTEGRITY_EXCEPTION",
"message": "...", "hint": "validateChannelReadinessOnProvisioning rejected the write — most commonly missing consent; run service-de-channel-settings-configure first"}
{"ok": false, "kind": "provisioning-error", "mcuId": "0gL...", "errorReason": "MetaRegistrationFailed", "errorDetails": "..."}
{"ok": false, "kind": "verification-failed", "hint": "WhatsApp phone number verification failed or was declined by user"}
{"ok": false, "kind": "verification-request-failed", "errorCode": "...", "message": "...", "hint": "Could not request verification code from Meta"}Failure — auth / transport:
{"ok": false, "kind": "auth", "hint": "OAuth token invalid / expired — run 'sf org login web'"}
{"ok": false, "kind": "transport", "status": 500, "message": "..."}Stage 1: Precondition checks
Read the channel, then its MCU, as two separate SOQL calls. A combined subquery would be cheaper but fails on orgs where the child relationship is unnameable (see gotcha #4).
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, DeveloperName, MessageType, IsActive, SessionHandlerId, FallbackQueueId, MessagingPlatformKey FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" \
--json > /tmp/amc-precheck-channel.json
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, DeploymentStatus, DeploymentType, ErrorReason, ErrorDetails FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" \
--json > /tmp/amc-precheck-mcu.jsonLet channel = /tmp/amc-precheck-channel.json records[0] and mcu = /tmp/amc-precheck-mcu.json records[0]:
| Condition | Envelope |
|---|---|
| Channel query returned 0 records | {ok:false, kind:"channel-missing", hint:"MessagingChannel id not found"} |
channel.IsActive === true | {ok:true, noop:true, channelId, message:"Channel already active"} — return |
More from forcedotcom
Use this skill in a project
Open any RiverX project, search the Skills panel for forcedotcom/sf-skills/service-de-channel-activate, and install it in one click.
