Important: As of Jan 01, 2022, the Chat Conversations API is in maintenance mode and will not be receiving new features. Bug and security fixes will continue when required. Messaging customers can purchase a Sunshine Conversations license and use that platform to add third-party or custom bots to their workflow as a replacement option.

The Chat Conversations API allows your application to act as a Zendesk Chat agent and interact with your website visitors.

The API uses the GraphQL data query language to give you more flexibility. GraphQL lets you:

  • Define precisely the data your application needs
  • Minimize the number of requests
  • Validate your queries with the GraphQL type system

For more information on GraphQL, see GraphQL resources below.

You can call the Conversations API in one of the two ways:

  • Make HTTP requests to
  • Send requests to an authenticated WebSocket address (wss://{session_id}:{client_id}).

HTTP requests are mainly for authentication. All other requests should be made through a WebSocket connection.

Topics covered in this article:


The Conversations API has the following limitations:

  • Does not support roles and permission. Agents whose sessions were created by Conversations API do not comply with the Roles and Permissions settings of their account.
  • Does not fully support Assigned chat routing. Assigned chat routing will only work if the agent that uses the Conversations API is the only agent in the department.
  • Interacts only with website visitors that came from either the Zendesk Web Widget (native or customized with Mobile SDK) or the Zendesk Chat Widget (native or customized with Mobile SDK/Web SDK).

GraphQL schema documentation

The GraphQL schema documentation of the Conversations API is available at The schema contains the full detail of all operations that the Conversations API supports.

Tip: You can use the ChromiQL Google Chrome extension or the GraphQL IDE to help you construct your GraphQL queries with autocompletion and error detection. Just set the endpoint to Note that you can't use them to make actual requests since they don't have your authentication information.

Key concepts


Visitor - Someone who visits your website with the embedded Chat widget.

Widget - An embedded Zendesk Chat interface.

Agent - Someone who is serving the visitor chat.

Channel - Visitor and agent exchange chat messages over a channel.

GraphQL operations

The Conversations API supports the 3 main GraphQL operations:

  • Mutation - Mutation operations modify data.
  • Query - Query operations let you ask for data that you're interested in.
  • Subscription - Subscription operations let you subscribe to topics from which you can get real-time updates.

Note: The Conversations API uses the Relay connection model to provide a standard mechanism for slicing and paginating result sets. See Relay Cursor Connections Specification on the Facebook Github website for more information.

API query flow

To begin querying the API, you must first be authenticated using the startAgentSession mutation through an HTTP request to The endpoint responds with a session ID, a client ID, and a WebSocket URL that you can use to establish a WebSocket connection.

# Sample WebSocket connection url# Format: wss://{session_id}:{client_id}

Once the WebSocket connection is established, you can proceed to query the API through the connection.

Under some circumstances, your session can become invalid. You'll receive an end of service (EOS) signal. See End of service signal.

Getting started

This section describes how to start using the Conversations API. The complete source code for the application described in this section is available online. See Sample application code.

Topics covered in this section:

Start agent session

To start an agent session with OAuth authentication, first generate an OAuth access token with read, write, and chat scopes as described in OAuth Authentication on the Zendesk developer portal. Once the access token is generated, call the startAgentSession mutation with the token in an HTTP request to

const request = require('superagent');
const CHAT_API_URL = '';
const query = `mutation {  startAgentSession(access_token: "my_oauth_access_token") {    websocket_url    session_id    client_id  }}`;
request  .post(CHAT_API_URL)  .set({    'Content-Type': 'application/json'  })  .send({ query });

The example uses the superagent library for creating the HTTP request. You can also use your preferred HTTP request library.

After successfully starting an agent session, you should get a response like the following if the credentials are valid:

{  "data": {    "startAgentSession": {      "websocket_url":"wss://",      "session_id":"8b40lm",      "client_id":"9012841289"    }  }}

Establish a WebSocket connection using the value of websocket_url from the response.

const WebSocket = require('ws');
const webSocket = new WebSocket(  'wss://');
webSocket.on('open', () => {  // do something});

The example uses the ws library for the client-side connection. You can use your preferred websocket client library.

Subscribe to incoming messages

Subscribing to incoming messages allows you to listen for any chat messages from either an unserved visitor or a channel served by you.

const messageSubscriptionQuery = {  payload: {    query: `subscription {      message {        node {          id          content          channel {            id          }          from {            __typename            display_name          }        }      }    }`  },  type: 'request',  id: REQUEST_ID.MESSAGE_SUBSCRIPTION};

The messageSubscriptionQuery variable contains a request for certain data (id, content, channel, from) from the Message type. See Message in the reference documentation for the fields you can query.

A unique request ID (REQUEST_ID.MESSAGE_SUBSCRIPTION) is also provided to identify the response for this request. Let’s listen to this ID.

webSocket.on('message', function(message) {  const data = JSON.parse(message);  let messageSubscriptionId;
  // Listen to successful message subscription request  if ( === REQUEST_ID.MESSAGE_SUBSCRIPTION) {    messageSubscriptionId =;  }});

If it's a successful subscription, you'll get a response like the following:

{  "payload": {    "data": {      "subscription_id": "a1gf13bjz13"    }  },  "id": 456}

The subscription_id property identifies the subscription.

webSocket.on('message', function(message) {  const data = JSON.parse(message);  let messageSubscriptionId;
  // Listen to successful message subscription request  if ( === REQUEST_ID.MESSAGE_SUBSCRIPTION) {    messageSubscriptionId =;  }
  // Listen to chat messages from the visitor  if (    data.sig === 'DATA' &&    data.subscription_id === messageSubscriptionId &&  ) {    const chatMessage =;    const sender = chatMessage.from;
    if (sender.__typename === TYPE.VISITOR) {      console.log(        `[message] Received: '${chatMessage.content}' from: '${          sender.display_name        }'`      );    }  }});

The special field sig DATA identifies any subscription data message. Your application should check for this field and listen for that subscription_id when you receive messages through your WebSocket connection.

Update agent status

Let’s ensure that the agent is online so that a visitor can view the widget.

const updateAgentStatusQuery = {  payload: {    query: `mutation {      updateAgentStatus(status: ONLINE) {        node {          id        }      }    }`  },  type: 'request',  id: REQUEST_ID.UPDATE_AGENT_STATUS};

Send a message from a visitor

Go to the web page where the Zendesk Chat widget is embedded. Type "Hi" to initiate a chat. Your application should be able to receive and print out the chat message.

[message] Received: 'Hi' from: 'Visitor 12345678'

Send a message to the visitor

You can send a message to the visitor too. Let’s echo back what the visitor sent every time we receive a message from the visitor.

webSocket.on('message', function(message) {  const data = JSON.parse(message);  let messageSubscriptionId;
  // Listen to successful message subscription request  if ( === REQUEST_ID.MESSAGE_SUBSCRIPTION) {    messageSubscriptionId =;  }
  // Listen to chat messages from the visitor  if (    data.sig === 'DATA' &&    data.subscription_id === messageSubscriptionId &&  ) {    const chatMessage =;    const sender = chatMessage.from;
    if (sender.__typename === TYPE.VISITOR) {      console.log(        `[message] Received: '${chatMessage.content}' from: '${          sender.display_name        }'`      );
      const sendMessageQuery = {        payload: {          query: `mutation {            sendMessage(channel_id: "${}", msg: "${            chatMessage.content          }") {              success            }          }`        },        type: 'request',        id: REQUEST_ID.SEND_MESSAGE      };
      webSocket.send(JSON.stringify(sendMessageQuery));    }  }});

Go back to the website and type 'Hello!'. You should get a 'Hello!' in reply.

Send a structured message to the visitor

You can also send structured messages to the visitor. One example is sending quick replies to provide reply suggestions to the visitor.

const sendQuickRepliesQuery = {  payload: {    query: `mutation {      sendQuickReplies(        channel_id: "${}",        msg: "We have the following options. Which one is your favorite?",        quick_replies: [          {            action: {              value: "My favorite is chocolate"            },            text: "Chocolate"          },          {            action: {              value: "My favorite is vanilla"            },            text: "Vanilla"          },          {            action: {              value: "My favorite is cookies and cream"            },            text: "Cookies and cream"          },          {            action: {              value: "My favorite is coconut"            },            text: "Coconut"          },          {            action: {              value: "My favorite is salted caramel"            },            text: "Salted caramel"          }        ],        fallback: {          msg: "We have the following options. Which one is your favorite?"          options: [            "Chocolate",            "Vanilla",            "Cookies and cream",            "Coconut",            "Salted caramel"          ]        }      ) {        success      }    }`  },  type: "request",  id: REQUEST_ID.SEND_QUICK_REPLIES};

In the example above, the quick_replies argument denote the quick replies that will be sent to the visitor. When the visitor clicks one of the quick replies, the visitor will send the selected custom value to the agent.

Note that the fallback argument can be used to provide a fallback experience for situations where structured messages are not supported. For more information, see Using structured messages in Zendesk Chat in Zendesk help.

Transfer to a department

You can also transfer the chat to a department. First, you will need to know what is the ID of the department that you want to transfer to.

Note: Department ID retrieved from Zendesk Chat REST API will not work.

const getDepartmentsQuery = {  payload: {    query: `query {      departments {        edges {          node {            id            name            status          }        }      }    }`  },  type: 'request',  id: REQUEST_ID.GET_DEPARTMENTS};

Then, transfer the channel to an online department.

const allDepartments =;const onlineDepartments = allDepartments.filter(  department => department.node.status === 'ONLINE');
if (onlineDepartments.length > 0) {  const pickRandomDepartment = Math.floor(  Math.random() * onlineDepartments.length  );  const onlineDepartment = onlineDepartments[pickRandomDepartment].node;
  const transferToDepartmentQuery = {    payload: {      query: `mutation {        transferToDepartment(          channel_id: "${channelToBeTransferred}",          department_id: "${}"        ) {          success        }      }`    },    type: 'request',    id: REQUEST_ID.TRANSFER_TO_DEPARTMENT  };

For full documentation of all the operations that Conversations API supports, see the GraphQL schema docs.

Sample application code

Go to the Zendesk Chat Conversations API Sample App on for the complete source code described in this article. The code is hosted in a cloud-based Node.js sandbox that you can fork and use yourself. See the comment section at the top of the sample app for additional instructions.

WebSocket request and response body structure

This section looks at the different body structures required for the different GraphQL operations when using the WebSocket protocol.

Topics covered in this section:

Query and mutation


{  "payload": graphQLRequest,  "type": "request",  "id": number}
idnumber | stringyesA client-generated id. This id is echoed in the response to match the response with the request
typestringyesType of request. It should be a string with value of "request"
payloadJSONyesRequest payload. See Request payload

Success response

{  "payload": graphQLResponse,  "id": number}
idnumber | stringyesEchoes back the request id
payloadJSONyesResponse payload. See Response payload

Start subscription


{  "payload": graphQLRequest,  "type": "request",  "id": number}

Success response

{  "payload": {    "data": {      "subscription_id": string    },  },  "id": number}

Use subscription_id to match subsequent subscription data with the subscription request and to stop the subscription.

Stop subscription


{  "payload": {    "subscription_id": string  },  "type": "stop_subscription",  "id": number}

Success response

{  "payload": { "data" : { "success": true } },  "id": number}

Subscription data

Success response

{  "payload": graphqlResponse,  "sig": "DATA",  "subscription_id": string}

The DATA sig indicates that the response is a subscription data.

Error response

{  "error_code": httpStatusCode,  "payload": graphQLErrorPayload | protocolErrorPayload,  "sig": "DATA",  "subscription_id": string}

Request payload

{  "query": `mutation updateVisitor {    updateVisitorInfo(display_name: "jim", visitor_id: "2.jlgwAAseGzxwEr") {      success    }  }
  mutation startSession($access_token: String!)  {    startAgentSession(access_token: $token) {      websocket_url      session_id      client_id    }  }`,  "operationName": "startSession",  "variables": {    "access_token": "e86562f972c467f84712869bf3ccb21bdbe1e1ba2eddaa2ae6b8696e003d1d1a"  }}
querystringyesGraphQL query that must conform with the GraphQL Query Language specification
variablesobjectnoEnable client to pass dynamic query's arguments
operationNamestringyes**Required if the query contains several named operations. Controls the operation that should be executed

Response payload

The response payload has the following JSON format, which follows the GraphQL specification.

dataobjectnoThe result of the execution of the requested operation
errorarraynoNon‐empty list of errors during the execution of graphQL operation. See "graphQLErrorPayload" in the example below

Error response

The response body of all errors looks as follows unless otherwise specified.

{  "errorCode": httpStatusCode,  "payload": graphQLErrorPayload | protocolErrorPayload,  "id": number}

The error response can specify one of the following types of payload errors: graphQLErrorPayload and protocolErrorPayload.


A GraphQL execution failure.

{  errors: [    {      name: "GRAPHQL_EXEC_ERROR",      message: "unknown error when resolve hello field",      UUID: "f4706a5c-5022-4dcf-b113-2baa0c848557"    },    ...  ]}

Each error comprises of the following attributes:

namestringError name/code for error handling
messagestringError message/details
UUIDstringGlobally unique identifier of the error

For handling error, the name attribute can be used to differentiate the error types. The following are error names that you can expect:

Error NameDescriptionRetryable
UNAUTHORIZEDThe request lacks valid authentication credentials. This error can be due to the agent being disabled or using an invalid access token.No
FORBIDDENThe server understood the request but refused to authorize it. This error can be due to your account not being active, your account not having API access, or your access token not having enough scope to access the Conversations API.No
RATE_LIMITEDYou sent too many requests in a given amount of time. See Rate limits.Yes
MAX_SUBYour subscription request was declined because the current session exceeded the subscriptions limit. See Rate limits.No
GRAPHQL_EXEC_ERROROur server received your request but was unable to fully execute it. This error can be due to an invalid request structure, invalid request parameters, or an internal server error.No
GRAPHQL_SUB_START_ERRORFailed to start a GraphQL subscription request. This error can be due to an invalid request structure or internal server error.Limited (see below)

Retryable means you can try making the same operation call again and best with exponential backoff. If Retryable is No, the response will always give you the same error. Exception: If you get a GRAPHQL_SUB_START_ERROR, you can retry three to five times in 30 seconds with exponential backoff but not indefinitely.

If you feel the error details aren't sufficient, you can request further debugging information by contacting support with the error's UUID.


A protocol error. Example: The Conversations API GraphQL server is unavailable.

{  "body": string}

End of service signal

End of service (EOS) signal indicates that the current agent session is no longer valid. This signal can be due to:

  • Concurrent login (making multiple Conversations API sessions with different session ID).
  • Trying to make a WebSocket connection to an unauthenticated WebSocket URL.
  • After a stopAgentSession request is called.

The signal will have the following structure:

{  "reason": "unexpected session state",  "sig": "EOS"}

If you want your application to resume its session after receiving an EOS signal, make a startAgentSession request and use the new authenticated WebSocket URL.

Rate limits

The Conversations API is rate limited. See Rate limits in the Chat Conversations API reference doc.

GraphQL resources

See the following resources to learn more about GraphQL:


If you have any questions about the Chat Conversations API, please check the Zendesk APIs community. Feel free to post a question if no one has asked it before.