Getting started with the Chat Conversations API
Important: Starting July 1, 2024, new Messaging customers will no longer have access to the Chat Conversations APIs. Existing Messaging customers will be able to access the Chat Conversations APIs as usual.
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
https://chat-api.zopim.com/graphql/request
- Send requests to an authenticated WebSocket address (
wss://chat-api.zopim.com/stream/0.1/graphql/{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:
- Limitations
- GraphQL schema documentation
- Key concepts
- Getting started
- Sample application code
- WebSocket request and response body structure
- Rate limits
- GraphQL resources
- FAQ
Limitations
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 https://zendesk.github.io/conversations-api/. 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 https://chat-api.zopim.com/graphql/request
. Note that you can't use them to make actual requests since they don't have your authentication information.
Key concepts
Terminology
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 https://chat-api.zopim.com/graphql/request
. 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://chat-api.zopim.com/stream/0.1/graphql/{session_id}:{client_id}
wss://chat-api.zopim.com/stream/0.1/graphql/8b40lm:9012841289
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 an agent session
- Subscribe to incoming messages
- Update agent status
- Send a message from a visitor
- Send a message to the visitor
- Send a structured message to the visitor
- Transfer to a department
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 https://chat-api.zopim.com/graphql/request
.
const request = require('superagent');
const CHAT_API_URL = 'https://chat-api.zopim.com/graphql/request';
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://chat-api.zopim.com/stream/0.1/graphql/8b40lm:9012841289",
"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://chat-api.zopim.com/stream/0.1/graphql/8b40lm:9012841289'
);
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
};
webSocket.send(JSON.stringify(messageSubscriptionQuery));
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 (data.id === REQUEST_ID.MESSAGE_SUBSCRIPTION) {
messageSubscriptionId = data.payload.data.subscription_id;
}
});
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 (data.id === REQUEST_ID.MESSAGE_SUBSCRIPTION) {
messageSubscriptionId = data.payload.data.subscription_id;
}
// Listen to chat messages from the visitor
if (
data.sig === 'DATA' &&
data.subscription_id === messageSubscriptionId &&
data.payload.data
) {
const chatMessage = data.payload.data.message.node;
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
};
webSocket.send(JSON.stringify(updateAgentStatusQuery));
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 (data.id === REQUEST_ID.MESSAGE_SUBSCRIPTION) {
messageSubscriptionId = data.payload.data.subscription_id;
}
// Listen to chat messages from the visitor
if (
data.sig === 'DATA' &&
data.subscription_id === messageSubscriptionId &&
data.payload.data
) {
const chatMessage = data.payload.data.message.node;
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: "${chatMessage.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: "${chatMessage.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
};
webSocket.send(JSON.stringify(sendQuickRepliesQuery));
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
};
webSocket.send(JSON.stringify(getDepartmentsQuery));
Then, transfer the channel to an online department.
const allDepartments = data.payload.data.departments.edges;
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: "${onlineDepartment.id}"
) {
success
}
}`
},
type: 'request',
id: REQUEST_ID.TRANSFER_TO_DEPARTMENT
};
webSocket.send(JSON.stringify(transferToDepartmentQuery));
}
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 codesandbox.io 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
- Start subscription
- Stop subscription
- Subscription data
- Request payload
- Response payload
- Error response
- End of service signal
Query and mutation
Request
{
"payload": graphQLRequest,
"type": "request",
"id": number
}
Field | Type | Required | Description |
---|---|---|---|
id | number | string | yes | A client-generated id. This id is echoed in the response to match the response with the request |
type | string | yes | Type of request. It should be a string with value of "request" |
payload | JSON | yes | Request payload. See Request payload |
Success response
{
"payload": graphQLResponse,
"id": number
}
Field | Type | Non-null | Description |
---|---|---|---|
id | number | string | yes | Echoes back the request id |
payload | JSON | yes | Response payload. See Response payload |
Start subscription
Request
{
"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
Request
{
"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"
}
}
Attributes | Type | Required | Description |
---|---|---|---|
query | string | yes | GraphQL query that must conform with the GraphQL Query Language specification |
variables | object | no | Enable client to pass dynamic query's arguments |
operationName | string | yes* | *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.
Attributes | Type | Required | Description |
---|---|---|---|
data | object | no | The result of the execution of the requested operation |
error | array | no | Nonâ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.
graphQLErrorPayload
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:
Attributes | Type | Description |
---|---|---|
name | string | Error name/code for error handling |
message | string | Error message/details |
UUID | string | Globally unique identifier of the error |
For handling error, the name
attribute can be used to differentiate the error types. The following are error name
s that you can expect:
Error Name | Description | Retryable |
---|---|---|
UNAUTHORIZED | The request lacks valid authentication credentials. This error can be due to the agent being disabled or using an invalid access token. | No |
FORBIDDEN | The 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_LIMITED | You sent too many requests in a given amount of time. See Rate limits. | Yes |
MAX_SUB | Your subscription request was declined because the current session exceeded the subscriptions limit. See Rate limits. | No |
GRAPHQL_EXEC_ERROR | Our 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_ERROR | Failed 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.
protocolErrorPayload
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:
- Introduction to GraphQL on the graphql.org website
- How to GraphQL website
- GraphQL specification website
- awesome-graphql on Github
FAQ
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.