> ## Documentation Index
> Fetch the complete documentation index at: https://docs.open.cx/llms.txt
> Use this file to discover all available pages before exploring further.

# Live Messaging

> Route chat, SMS, WhatsApp, and phone handoffs from OpenCX into the Salesforce console via Messaging for In-App and Web.

export const SalesforceLiveFlow = () => {
  const BEAM_COLOR = "#22c55e";
  const renderBeam = ({id, d, duration = 2.8, delay = 0, reverse = false}) => {
    const values = reverse ? {
      x1: "110%;-10%",
      x2: "120%;0%"
    } : {
      x1: "-10%;110%",
      x2: "0%;120%"
    };
    return <g key={id}>
        <path d={d} stroke="currentColor" strokeOpacity="0.18" strokeWidth="1.5" fill="none" strokeLinecap="round" />
        <path d={d} stroke={`url(#${id})`} strokeWidth="2" fill="none" strokeLinecap="round" />
        <defs>
          <linearGradient id={id} gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="0">
            <stop offset="0%" stopColor={BEAM_COLOR} stopOpacity="0" />
            <stop offset="45%" stopColor={BEAM_COLOR} stopOpacity="1" />
            <stop offset="55%" stopColor={BEAM_COLOR} stopOpacity="1" />
            <stop offset="100%" stopColor={BEAM_COLOR} stopOpacity="0" />
            <animate attributeName="x1" values={values.x1} dur={`${duration}s`} begin={`${delay}s`} repeatCount="indefinite" />
            <animate attributeName="x2" values={values.x2} dur={`${duration}s`} begin={`${delay}s`} repeatCount="indefinite" />
          </linearGradient>
        </defs>
      </g>;
  };
  const CH_X = 80, ESD_X = 310, OMNI_X = 540, OCX_X = 800;
  const MID = 200, R = 40, CHR = 20;
  const channels = [{
    key: "widget",
    label: "Web chat",
    y: 100
  }, {
    key: "whatsapp",
    label: "WhatsApp",
    y: 160
  }, {
    key: "sms",
    label: "SMS",
    y: 220
  }, {
    key: "phone",
    label: "Phone",
    y: 280
  }];
  const chBeams = channels.map((ch, i) => ({
    id: `sfl-ch-${i}`,
    d: `M ${CH_X + CHR},${ch.y} C ${CH_X + CHR + 80},${ch.y} ${ESD_X - R - 60},${MID} ${ESD_X - R},${MID}`,
    delay: i * 0.3
  }));
  const b12 = `M ${ESD_X + R},${MID} L ${OMNI_X - R},${MID}`;
  const b23 = `M ${OMNI_X + R},${MID} C ${OMNI_X + 80},${MID - 30} ${OCX_X - R - 80},${MID - 30} ${OCX_X - R},${MID}`;
  const b23r = `M ${OMNI_X + R},${MID} C ${OMNI_X + 80},${MID + 30} ${OCX_X - R - 80},${MID + 30} ${OCX_X - R},${MID}`;
  return <div className="zendesk-flow not-prose my-8 w-full">
      <div className="zendesk-flow-frame relative w-full overflow-hidden rounded-md border border-black/10 bg-white px-4 py-6 dark:border-white/10 dark:bg-zinc-950">
        <div className="zendesk-flow-grid pointer-events-none absolute inset-0" aria-hidden />
        <svg viewBox="0 0 880 380" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" className="relative z-[1] block w-full text-zinc-500 dark:text-zinc-400" role="img" aria-label="Salesforce MIAW flow: channels hit an Embedded Service Deployment, Omni-Channel routes to OpenCX via routing attributes">
          {chBeams.map(b => renderBeam({
    id: b.id,
    d: b.d,
    duration: 3.2,
    delay: b.delay
  }))}
          {renderBeam({
    id: "sfl-12",
    d: b12,
    duration: 2.4,
    delay: 0
  })}
          {renderBeam({
    id: "sfl-23",
    d: b23,
    duration: 2.6,
    delay: 0.4
  })}
          {renderBeam({
    id: "sfl-23r",
    d: b23r,
    duration: 2.6,
    delay: 1.8,
    reverse: true
  })}

          <g className="fill-zinc-500 dark:fill-zinc-400" style={{
    font: "500 10.5px ui-sans-serif, system-ui, sans-serif"
  }}>
            <text x={(ESD_X + OMNI_X) / 2} y={MID - 16} textAnchor="middle">route</text>
            <text x={(OMNI_X + OCX_X) / 2} y={MID - 42} textAnchor="middle">Origin=MIABOT · JWT identity</text>
            <text x={(OMNI_X + OCX_X) / 2} y={MID + 54} textAnchor="middle">AI reply · handoff</text>
          </g>

          {}
          {channels.map(ch => <g key={ch.key}>
              <circle cx={CH_X} cy={ch.y} r={CHR} className="fill-white stroke-black/10 dark:fill-zinc-900 dark:stroke-white/10" strokeWidth="1" />
              <g transform={`translate(${CH_X - 7} ${ch.y - 7})`} className="text-zinc-700 dark:text-zinc-200">
                {ch.key === "widget" && <g fill="currentColor"><path d="M1 2.5A1.5 1.5 0 0 1 2.5 1h11A1.5 1.5 0 0 1 15 2.5v7A1.5 1.5 0 0 1 13.5 11H9l-3 3v-3H2.5A1.5 1.5 0 0 1 1 9.5v-7z" /></g>}
                {ch.key === "whatsapp" && <g fill="currentColor"><path d="M8 1a7 7 0 0 0-6.03 10.55L1 15l3.55-.93A7 7 0 1 0 8 1Zm3.78 9.6c-.16.45-.94.86-1.31.89-.33.03-.76.04-1.22-.08a11 11 0 0 1-1.11-.4 8.6 8.6 0 0 1-3.3-2.88c-.25-.32-.65-.86-.65-1.64 0-.78.4-1.16.55-1.32.14-.16.3-.2.4-.2h.3c.1 0 .24 0 .37.28l.55 1.31c.05.1.08.21.02.33a1.23 1.23 0 0 1-.19.3l-.28.32c-.1.1-.19.22-.08.41.1.2.45.75.98 1.22.66.58 1.22.77 1.42.86.2.1.3.08.42-.05.12-.13.48-.56.61-.76.13-.2.26-.17.44-.1.18.06 1.15.55 1.35.65.2.1.33.15.38.24.05.1.05.5-.11.95Z" /></g>}
                {ch.key === "sms" && <g fill="currentColor"><path d="M3 1.5A1.5 1.5 0 0 0 1.5 3v8A1.5 1.5 0 0 0 3 12.5h2V15l2.8-2.5H13A1.5 1.5 0 0 0 14.5 11V3A1.5 1.5 0 0 0 13 1.5H3Z" /></g>}
                {ch.key === "phone" && <svg x="0" y="0" width="14" height="14" viewBox="0 0 20 20" fill="currentColor">
                    <path d="M2 3.5A1.5 1.5 0 0 1 3.5 2h1.148a1.5 1.5 0 0 1 1.465 1.175l.716 3.223a1.5 1.5 0 0 1-1.052 1.767l-.933.267c-.41.117-.643.555-.48.95a11.5 11.5 0 0 0 6.254 6.254c.395.163.833-.07.95-.48l.267-.933a1.5 1.5 0 0 1 1.767-1.052l3.223.716A1.5 1.5 0 0 1 18 14.352V15.5a1.5 1.5 0 0 1-1.5 1.5H15c-1.149 0-2.263-.15-3.326-.43A13.022 13.022 0 0 1 2.43 7.326 13.019 13.019 0 0 1 2 4V3.5Z" />
                  </svg>}
              </g>
              <text x={CH_X + CHR + 6} y={ch.y + 4} className="fill-zinc-700 dark:fill-zinc-200" style={{
    font: "500 11px ui-sans-serif, system-ui, sans-serif"
  }}>{ch.label}</text>
            </g>)}

          {}
          <g>
            <rect x={ESD_X - R} y={MID - R} width={R * 2} height={R * 2} rx="10" className="fill-white stroke-black/10 dark:fill-zinc-900 dark:stroke-white/10" strokeWidth="1" />
            <foreignObject x={ESD_X - 20} y={MID - 20} width="40" height="24">
              <div className="flex h-full w-full items-center justify-center">
                <img src="/images/integrations/salesforce.svg" alt="" className="h-6 w-6 object-contain" />
              </div>
            </foreignObject>
            <text x={ESD_X} y={MID + 10} textAnchor="middle" className="fill-zinc-700 dark:fill-zinc-200" style={{
    font: "600 10.5px ui-sans-serif, system-ui, sans-serif"
  }}>Embedded</text>
            <text x={ESD_X} y={MID + 22} textAnchor="middle" className="fill-zinc-700 dark:fill-zinc-200" style={{
    font: "600 10.5px ui-sans-serif, system-ui, sans-serif"
  }}>Service</text>
            <text x={ESD_X} y={MID + R + 20} textAnchor="middle" className="fill-zinc-500 dark:fill-zinc-400" style={{
    font: "400 10.5px ui-sans-serif, system-ui, sans-serif"
  }}>deployment</text>
          </g>

          {}
          <g>
            <rect x={OMNI_X - R} y={MID - R} width={R * 2} height={R * 2} rx="10" className="fill-white stroke-black/10 dark:fill-zinc-900 dark:stroke-white/10" strokeWidth="1" />
            {}
            <g transform={`translate(${OMNI_X - 14} ${MID - 14})`} className="fill-none stroke-zinc-700 dark:stroke-zinc-200" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="14" cy="14" r="4" />
              <path d="M 4 14 L 10 14 M 18 14 L 24 14 M 14 4 L 14 10 M 14 18 L 14 24" />
            </g>
            <text x={OMNI_X} y={MID + 10} textAnchor="middle" className="fill-zinc-700 dark:fill-zinc-200" style={{
    font: "600 10.5px ui-sans-serif, system-ui, sans-serif"
  }}>Omni</text>
            <text x={OMNI_X} y={MID + 22} textAnchor="middle" className="fill-zinc-700 dark:fill-zinc-200" style={{
    font: "600 10.5px ui-sans-serif, system-ui, sans-serif"
  }}>Channel</text>
            <text x={OMNI_X} y={MID + R + 20} textAnchor="middle" className="fill-zinc-500 dark:fill-zinc-400" style={{
    font: "400 10.5px ui-sans-serif, system-ui, sans-serif"
  }}>routing attrs</text>
          </g>

          {}
          <g>
            <circle cx={OCX_X} cy={MID} r={R + 4} className="fill-none" stroke={BEAM_COLOR} strokeOpacity="0.25" strokeWidth="1.5">
              <animate attributeName="r" values={`${R + 2};${R + 12};${R + 2}`} dur="3.6s" repeatCount="indefinite" />
              <animate attributeName="stroke-opacity" values="0.35;0;0.35" dur="3.6s" repeatCount="indefinite" />
            </circle>
            <circle cx={OCX_X} cy={MID} r={R} className="fill-white stroke-black/10 dark:fill-zinc-900 dark:stroke-white/10" strokeWidth="1" />
            <foreignObject x={OCX_X - 30} y={MID - 16} width="60" height="32">
              <div className="flex h-full w-full items-center justify-center">
                <img src="/logo-light.svg" alt="" className="block h-full w-full object-contain dark:hidden" />
                <img src="/logo.svg" alt="" className="hidden h-full w-full object-contain dark:block" />
              </div>
            </foreignObject>
            <text x={OCX_X} y={MID + R + 22} textAnchor="middle" className="fill-zinc-700 dark:fill-zinc-200" style={{
    font: "600 12px ui-sans-serif, system-ui, sans-serif"
  }}>OpenCX</text>
            <text x={OCX_X} y={MID + R + 36} textAnchor="middle" className="fill-zinc-500 dark:fill-zinc-400" style={{
    font: "400 10.5px ui-sans-serif, system-ui, sans-serif"
  }}>AI messaging user</text>
          </g>
        </svg>
      </div>
    </div>;
};

<Tooltip tip="Salesforce's real-time messaging layer for web, mobile, and third-party channels. Conversations route through Omni-Channel to available agents.">Messaging for In-App and Web</Tooltip> is the path for every real-time channel — web chat, SMS, WhatsApp, and phone. Conversations land in the Salesforce console as live messaging threads your reps pick up with the full AI transcript attached. For email, use [Email Cases](/integrations/salesforce/email-cases) instead.

<SalesforceLiveFlow />

<Info>
  Setup takes about 20 minutes. You need a Salesforce admin who has configured an <Tooltip tip="A Salesforce configuration that hosts messaging channels and routes conversations through Omni-Channel to available agents.">Embedded Service Deployment</Tooltip> with Omni-Channel routing, and admin access in OpenCX.
</Info>

## Before you start

<AccordionGroup>
  <Accordion title="Salesforce edition with Messaging for In-App and Web" icon="circle-check">
    Service Cloud with Messaging for In-App and Web enabled. Contact your Salesforce admin if the Embedded Service Deployments section is missing from Setup.
  </Accordion>

  <Accordion title="Embedded Service Deployment configured" icon="gear">
    An Embedded Service Deployment must already exist in Salesforce with Omni-Channel routing configured. OpenCX connects to this deployment — it does not create one for you.
  </Accordion>

  <Accordion title="RSA key pair for JWT authentication" icon="lock">
    OpenCX signs identity tokens with an RSA private key. You register the matching public key in Salesforce. Generate the pair before starting setup.
  </Accordion>

  <Accordion title="Owner or admin in OpenCX" icon="key">
    Required to save integration settings in [Settings → Integrations](https://platform.open.cx/settings/integrations).
  </Accordion>
</AccordionGroup>

## Setup

<Steps>
  <Step title="Gather your Salesforce identifiers">
    You need three values from Salesforce Setup:

    | Identifier            | Where to find it                                                                           |
    | --------------------- | ------------------------------------------------------------------------------------------ |
    | **Instance URL**      | Your Salesforce domain (e.g. `https://yourcompany.my.salesforce.com`).                     |
    | **ES Developer Name** | **Setup → Embedded Service Deployments** → select your deployment → copy the **API Name**. |
    | **Salesforce Org ID** | **Setup → Company Information → Organization ID**.                                         |
  </Step>

  <Step title="Generate and register JWT keys">
    1. Generate an RSA key pair (2048-bit or higher).
    2. In Salesforce, open your Embedded Service Deployment's **Auth Key** settings and register the **public key** (PEM format).
    3. Note the **Auth Key ID** Salesforce assigns after registration — you need it in the next step.

    <Warning>
      Keep the private key secure. Anyone with the private key can sign identity tokens on behalf of your organization.
    </Warning>
  </Step>

  <Step title="Enter credentials in OpenCX">
    In your [OpenCX dashboard](https://platform.open.cx/settings/integrations), open **Salesforce** and select the **MIAW** tab.

    | Field                 | Value                                                                          |
    | --------------------- | ------------------------------------------------------------------------------ |
    | **Instance URL**      | Your Salesforce domain (include `https://`).                                   |
    | **ES Developer Name** | The API name from your Embedded Service Deployment.                            |
    | **Salesforce Org ID** | The Organization ID from Company Information.                                  |
    | **Auth Public Key**   | The public key in PEM format (include `-----BEGIN PUBLIC KEY-----` headers).   |
    | **Auth Private Key**  | The private key in PEM format (include `-----BEGIN PRIVATE KEY-----` headers). |
    | **Auth Key ID**       | The Key ID Salesforce assigned when you registered the public key.             |

    Click **Save**.

    <Note>
      The private key is stored encrypted. After saving, the dashboard displays only a redacted preview — the full key is never shown again.
    </Note>
  </Step>

  <Step title="Verify end to end">
    Send a test message on a connected channel (web widget, WhatsApp, or SMS). Escalate the conversation.

    1. A messaging thread appears in the Salesforce console with the AI transcript.
    2. Reply from the Salesforce console.
    3. The reply lands back on the original channel within a couple of seconds.

    If any step fails, jump to [Troubleshooting](/integrations/salesforce/troubleshooting#live-messaging).
  </Step>
</Steps>

## How handoff lands in Salesforce

```mermaid theme={"dark"}
flowchart LR
  A[Contact message] --> B{AI can resolve?}
  B -- Yes --> C[AI replies on channel]
  B -- No --> D[Handoff]
  D --> E[Messaging thread in Salesforce]
  E --> F[Rep replies in console]
  F --> G[Reply lands on original channel]
```

When <Tooltip tip="The decision point where the AI escalates a conversation to a human agent." cta="Handoff settings" href="/handoff/introduction">handoff</Tooltip> triggers, OpenCX:

1. Generates a JWT identity token for the contact, signed with your private key.
2. Obtains a messaging access token from Salesforce.
3. Builds a transcript with the AI summary and conversation history.
4. Extracts routing attributes from the session — channel, language, priority, and session ID.
5. Creates a conversation in Salesforce with routing attributes attached.
6. Sends the transcript as the opening message so your rep has full context.
7. Rep replies sync back to the OpenCX session in real time.

<Note>
  OpenCX retries handoff up to 3 times with increasing delays if Salesforce is temporarily unreachable.
</Note>

## Routing attributes

OpenCX attaches these attributes to every messaging conversation. Your <Tooltip tip="Salesforce's intelligent routing engine that distributes work items to agents based on availability, capacity, and skills.">Omni-Channel</Tooltip> flow can read them for routing decisions.

| Attribute            | Value                                  | Purpose                                         |
| -------------------- | -------------------------------------- | ----------------------------------------------- |
| `OpencxSessionId`    | Session UUID                           | Trace back to the OpenCX session.               |
| `Chat_Consumer_Type` | `Authenticated` or `Non-Authenticated` | Differentiate identified vs anonymous contacts. |
| `Origin`             | `MIABOT`                               | Identifies conversations that came from OpenCX. |
| `Priority`           | `Low` (default)                        | Baseline routing priority.                      |
| `Chat_language`      | Detected language code                 | Route to language-specific queues.              |

<Info>
  Additional attributes may populate from session and contact data depending on your organization's configuration. Your Omni-Channel flow must reference the attribute names to use them for routing.
</Info>

## Disconnecting

In OpenCX, open the Salesforce integration, select the MIAW tab, and click **Delete**. Active conversations finish on their current channel; no data is deleted from Salesforce.

***

## Related Documentation

<CardGroup cols={2}>
  <Card title="Channels" icon="grid-2" href="/integrations/salesforce/channels/widget">
    Per-channel implementation details for Widget, WhatsApp, SMS, Phone, and Email.
  </Card>

  <Card title="Email Cases" icon="envelope" href="/integrations/salesforce/email-cases">
    The companion path for email handoff.
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/integrations/salesforce/troubleshooting">
    Access token errors, missing conversations, routing issues.
  </Card>

  <Card title="Handoff settings" icon="user-group" href="/handoff/introduction">
    Global handoff rules and office hours.
  </Card>
</CardGroup>
