service-de-channel-activate

Activate an Enhanced `MessagingChannel` (WhatsApp / Apple / Facebook / SMS / RCS) by PATCHing `MessagingChannelUsage.DeploymentStatus` from `Disabled` to `Provisioning` via the standard REST sobject endpoint. The UDD save-hook fires on the REST write, dispatches by `MessageType` for the external cal

By forcedotcom · 596 installs

npx skills add forcedotcom/sf-skills --skill service-de-channel-activate

Source repository · Upstream listing

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: 1. MessagingChannelUsageFunctions.validateBeforeSave runs validateDeploymentStatus → validateChannelReadinessOnProvisioning . For WhatsApp this confirms consent is configured. Rejected writes return HTTP 400 FIELD INTEGRITY EXCEPTION . 2. MessagingChannelUsageFunctions.saveHook PostStmtExecuteOnce fires unconditionally after the UPDATE statement. It calls MessagingChannelUsageFunctionsHelper.handlePostSave which registers a post commit TransactionObserver . 3. At commit, the observer calls ConversationChannelUsageDeploymentStatusService.handleProvisioning (inherited from AbstractChannelUsageDeploymentStatusService ), which: Calls runProvisioning — switch dispatches by MessageType for 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 → default branch, no external callout, no network wait. On success: writes DeploymentStatus = 'Active' via PLSQL. On failure: writes DeploymentStatus = 'Error' plus ErrorReason / ErrorDetails . 4. Inside the same observer, a second pass syncs MessagingChannel.IsActive = true once MCU reaches Active (the isTransitioningStatus flag skips the flip while status is still Provisioning ). 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 → Provisioning is not in getValidAPIStatusTransitions() ) — the PATCH would return 400. The Stage 1 precondition check catches this and emits noop:true . Routing isn't configured. activateChannelUsage used to fail with LiveMessageSetupException / nullQueueId at the Apex entry point. With the REST path the same guard lives in validateChannelReadinessOnProvisioning — write with SessionHandlerId=null && FallbackQueueId=null → 400 FIELD INTEGRITY EXCEPTION . Run service de channel routing configure first. 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 char MessagingChannel.Id (prefix 0Mj ). The channel must already exist with a non null SessionHandlerId or FallbackQueueId . {ORG ALIAS} — optional; the sf CLI target org alias. Default: whatever sf config get target org returns. Used for OAuth and SOQL reads. {API VERSION} — optional; REST API version. Default: 68.0 . Any version where MessagingChannelUsage is 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: Success — no op (already active): Failure — precondition not met: Failure — validator or server side provisioning error: Failure — auth / transport: 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). Let 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 channel.SessionHandlerId == null && channel.FallbackQueueId == null {ok:false, kind:"no routing", hint:"run service de channel routing configure first"} MCU query returned 0 records {ok:false, kind:"no mcu", hint:"no MessagingChannelUsage row for this channel — run the insertion skill first"} Otherwise Record {MCU ID} = mcu.Id , {MESSAGE TYPE} = channel.MessageType , {MESSAGING PLATFORM KEY} = channel.MessagingPlatformKey , {INITIAL MCU STATUS} = mcu.DeploymentStatus and continue to Stage 2. Also record {T0} (epoch ms at start of Stage 2) so the final envelope can report durationMs . Stage 1.1: Fast path for already provisioning MCU If {INITIAL MCU STATUS} === "Provisioning" — the MCU is already mid flight from a prior call in this transaction window. Skip Stage 2 (firing the PATCH) entirely and jump to Stage 3 (verification). This is a rare race guard: the observer is synchronous with the PATCH, so by the time the caller sees the 204 the status is already terminal ( Active or Error ) — Provisioning should be invisible from outside. If we do see it in the precheck, something wrote Provisioning in a separate DML and the observer is still mid flight — don't fire a second PATCH. Stage 2: PATCH MessagingChannelUsage.DeploymentStatus = "Provisioning" Use sf api request rest so authentication stays inside the CLI's transport — no OAuth token is ever extracted into shell state. Fire the PATCH. This call can take 15 30 seconds for WhatsApp — the observer runs synchronously, including Meta's /register round trip. sf api request rest has no separate client side timeout to raise; it waits on the underlying HTTP call. include prints the HTTP status/headers block ahead of the (typically empty, on 204) body — read the status line from that block rather than a w style trailing marker. Classify by HTTP status: Status Body Handling 204 empty Success. The observer ran to completion; MCU is Active or Error . Continue to Stage 3 to read the terminal state. 400 [{errorCode:"FIELD INTEGRITY EXCEPTION", message:"..."}] Validator rejection. See table below. 400 [{errorCode:"INVALID FIELD FOR INSERT UPDATE" or "MALFORMED ID", ...}] Skill bug — the MCU ID from Stage 1 was wrong, or the body shape is off. Emit {ok:false, kind:"transport", status:400, message} . 401 (usually empty) Bearer token invalid. Emit {ok:false, kind:"auth", hint:"OAuth token invalid / expired — run 'sf org login web'"} . 403 [{errorCode:"INSUFFICIENT ACCESS"}] User lacks perm to write DeploymentStatus . Emit {ok:false, kind:"business", errorCode:"INSUFFICIENT ACCESS", message} . 5xx varies Transport. The observer may have partially committed — Stage 3's SOQL is the source of truth. Read MCU state; if it's Active , report success with a warning; if Error or still Disabled , classify as {ok:false, kind:"transport", status, message} . Known 400 FIELD INTEGRITY EXCEPTION messages: Message fragment Meaning Envelope "invalid deployment status transition" The current status doesn't allow → Provisioning (e.g. MCU is already Active — Stage 1 should have caught this, but there's a race window). Re read MCU; if now Active , emit success noop. If Provisioning , Stage 3 poll. Otherwise emit {ok:false, kind:"readiness failed", ...} . "consent" / "keyword" / mentions of STOP/HELP validateChannelReadinessOnProvisioning rejected — channel doesn't have required consent configured. {ok:false, kind:"readiness failed", errorCode:"FIELD INTEGRITY EXCEPTION", message, hint:"channel requires a ConsentType and a matching MsgChannelLanguageKeyword record (opt out keyword + confirmation) before activation — run service de channel consent configure"} . "routing" / "queue" / "SessionHandler" Routing precondition (Stage 1 should have caught, but the validator re checks). {ok:false, kind:"no routing", message, hint:"run service de channel routing configure first"} . other Unrecognized validator error. {ok:false, kind:"readiness failed", errorCode, message} . Stage 3: Read the terminal MCU state The PATCH is synchronous, so by the time we're here the MCU is Active or Error — no polling. Read once: Status Handling Active Continue to Stage 4. Error with ErrorReason === "VERIFICATION REQUIRED" For WhatsApp channels only: phone number needs OTP verification. Continue to Stage 3.2 (WhatsApp verification flow). Error (other) Emit {ok:false, kind:"provisioning error", mcuId, errorReason, errorDetails} . Provisioning Unexpected — the observer was supposed to terminate before the PATCH returned 204. Fall through to a defensive poll (see Stage 3.1). Disabled The PATCH returned 204 but the write didn't take? Emit {ok:false, kind:"transport", message:"PATCH returned 204 but MCU is still Disabled — observer didn't commit"} . Stage 3.2: WhatsApp phone number verification (only if ErrorReason === "VERIFICATION REQUIRED") This sub flow only runs for WhatsApp channels when activation fails with ErrorReason === "VERIFICATION REQUIRED" — the phone number needs OTP verification with Meta before it can be registered. If the MCU comes back with ErrorReason === "VERIFICATION REQUIRED" (WhatsApp only), load references/phone verification.md and follow it to drive the phone number verification sub flow (request code → prompt user → validate code → retry activation once). Stage 3.1: Defensive poll (only if Stage 3 saw Provisioning ) The observer's external callout block (WhatsApp/Facebook/SMS) runs synchronously inside the PATCH request — there's no async queue indirection in runProvisioning for any message type (verified against the switch/case in ConversationChannelUsageDeploymentStatusService ). So a Provisioning status at Stage 3 should not happen in steady state. Possible causes if it does: an exception thrown after the external call s