Getting the CSAT ratings of tickets
A customer satisfaction, or CSAT, survey encourages your customers to provide feedback about their support experience by rating their solved tickets. The survey is designed to maximize the response rate by being quick and simple while also gathering the essential data.
If you are using the legacy CSAT option, you must deactivate the legacy CSAT and manually deactivate any custom CSAT automations and triggers before you can activate the updated CSAT option. You can have only one CSAT option activated at a time.
This article describes how to retrieve the survey details of a ticket. This involves getting a survey response id from a recent audit record of the ticket, and then using that id to retrieve the survey details.
Note: CSAT survey responses are available on the Support Professional plan and above, and the Zendesk Suite Growth plan and above.
Getting detailed CSAT responses
Getting detailed CSAT responses is a three-step process:
- Export ticket data to find which tickets have been updated recently.
- Use the List Audits for a Ticket endpoint to find tickets in the exported tickets that have received a survey response, and get the survey response id.
- Use the Show Survey Responses endpoint to extract survey responses.
Finding tickets that have been updated recently
You can use the Incremental Ticket Export API to find tickets that have been updated recently. Some of these updates might have been caused by a user submitting a CSAT survey response for a solved ticket.
curl https://{subdomain}.zendesk.com/api/v2/incremental/tickets? \
start_time={unix_epoch_time} -v -u {email_address}/token:{api_token}
Example:
{
{
"url": "https://mytest.zendesk.com/api/v2/tickets/45.json",
"id": 45,
"external_id": null,
...
"custom_fields": [],
"satisfaction_rating": {
"score": "bad",
"id": 7811046666877,
"comment": null,
"reason": "The issue took too long to resolve",
"reason_id": 7811046657149
},
...
]
}
Getting tickets with a survey response id
Now use the List Audits for a Ticket endpoint to get the audit logs associated with each ticket returned by the incremental ticket export.
You can refine your exported ticket list by excluding any tickets that have a satisfaction rating score of "unoffered." This score indicates that the customer did not receive a survey.
"satisfaction_rating": {
"score": "unoffered"
},
When viewing the ticket audits, search for the survey_response_id
property, and copy its value. Each audit event of the type SurveyResponseSubmitted
contains a non-null survey_response_id
field, which can be also used to filter the events.
curl https://{subdomain}.zendesk.com/api/v2/tickets/{ticket_id}/audits \
-v -u {email_address}/token:{api_token}
Example:
{
"audits": [
{
"id": 8291457285373,
"ticket_id": 45,
"created_at": "2024-09-30T11:57:52Z",
"author_id": 53954053,
...
{
"id": 8291460961533,
"type": "SurveyResponseSubmitted",
"survey_type": "CustomerSatisfaction",
"survey_response_id": "01J91CQ460QCKK0S1SKMGR27RN",
"assigned_user_id": 3597283,
"assigned_group_id": 562253
}
...
}
Getting the survey responses
Once you get the survey response id, you can use the Show Survey Response endpoint to get detailed feedback from that survey.
curl https://{subdomain}.zendesk.com/api/v2/guide/en-us/survey_responses/ \
{survey_response_id} -v -u {email_address}:/token:{api_token}
Example:
{
"survey_response": {
"id": "01J91CQ460QCKK0S1SKMGR27RN",
"expires_at": "2024-10-28T11:58:10.111Z",
"survey_id": "01HET0MV6FDSBBNX1VPYZHA1S3",
"survey_version": 34,
"answers": [
{
"type": "rating_scale",
"rating": 1,
"rating_category": "bad",
"question": {
"type": "rating_scale_numeric",
"id": "01HET0MQ2HG2S3KS46M8F8HE11",
"alias": "customer satisfaction rating",
"sub_type": "customer_satisfaction",
"headline": {
"type": "static",
"value": "How would you rate the support you received?"
},
"options": [
{
"rating": 1,
"label": {
"type": "static",
"value": "Very unsatisfied"
}
},
...
}
Review the following property values for customer feedback:
rating
rating_category
value
Integrating the data
With both the ticket information and detailed survey responses, you can perform analyses, such as linking customer satisfaction scores directly to specific ticket handling times, root cause analysis, or agent interactions.
You can also identify customers who provided low survey ratings and had unresolved or problematic tickets. You can follow up with these customers to address their concerns and improve their experience.
Automation and regular updates
If you have a high volume of tickets, consider using automation. For example:
- Automate the API calls and data processing using a script or a server-side application.
- Schedule these scripts to run at regular intervals, such as every 24 hours, aligning with the
start_time
parameter based on the last successful fetch. - Store results in a database for historical tracking and trend analysis.
Example automation script
The following Python script retrieves survey responses of tickets by performing the following steps:
- Gets all tickets that have been updated since a specific date and time, and that have a valid satisfaction rating score
- Adds survey response ids to the tickets
- Uses the survey response ids to add the survey responses to the tickets
- Prints the survey response of each ticket to the console
Requirements
Disclaimer
Zendesk provides this example script for instructional purposes only. Zendesk doesn't provide support for the example code. Zendesk doesn't support third-party technologies such as Python or command-line tools.
Sample script
Run python get_csat_report.py
on the command line.
File name: get_csat_report.py
import requests
import arrow
SUBDOMAIN = 'your_subdomain'
EMAIL = 'your_email@example.com'
API_TOKEN = 'your_api_token'
AUTH = f'{EMAIL}/token', API_TOKEN
ROOT = f'https://{SUBDOMAIN}.zendesk.com/api/v2'
def main():
"""
Run `python get_csat_report.py` on the command line.
Prompts user for a ticket update start time as an epoch time. Example: 1717204386 (June 1, 2024).
"""
print('Enter a start time as a UNIX epoch time. See https://www.epochconverter.com/ to convert a time.')
start_time: str = input("Start time as UNIX epoch time: ")
tickets_with_satisfaction_score: list = get_tickets_with_satisfaction_score(start_time)
tickets_with_survey_response_ids: list = add_survey_response_ids(tickets_with_satisfaction_score, start_time)
tickets_with_survey_responses: list = add_survey_responses(tickets_with_survey_response_ids)
list_survey_responses(tickets_with_survey_responses)
def get_tickets_with_satisfaction_score(start_time: str) -> list:
"""
:param start_time: UNIX epoch time
:return: List of recently updated tickets that have a satisfaction score that is not "offered" or "unoffered"
"""
print('Getting updated tickets...')
updated_tickets: list = []
url = f'{ROOT}/incremental/tickets/cursor.json'
params = {'start_time': int(start_time)}
response = requests.get(url, params=params, auth=AUTH)
while url:
if response.status_code == 200:
data: dict = response.json()
updated_tickets.extend(data['tickets'])
# get next page
if not data['end_of_stream']:
url = data['after_url']
response = requests.get(url, auth=AUTH)
else:
print(f'Retrieved {len(updated_tickets)} updated tickets. '
f'The after cursor for your next export is {data["after_cursor"]}')
url = ''
else:
error = f'Failed to retrieve data, status code: {response.status_code}'
raise RuntimeError(error)
if not updated_tickets:
print('No ticket received a CSAT survey response during this period.')
exit()
updated_tickets_with_satisfaction_score: list = []
for ticket in updated_tickets:
if 'satisfaction_rating' in ticket:
if ticket['satisfaction_rating']['score'] not in ['offered', 'unoffered']:
updated_tickets_with_satisfaction_score.append(ticket)
return updated_tickets_with_satisfaction_score
def add_survey_response_ids(tickets: list, start_time: str) -> list:
"""
:param tickets: List of recently updated tickets with a satisfaction score
:param start_time: UNIX epoch time
:return: List of tickets with the survey response id included
"""
print('Getting survey response ids...')
tickets_with_survey_response_ids: list = []
for ticket in tickets:
url = f'{ROOT}/tickets/{ticket["id"]}/audits.json'
response = requests.get(url, auth=AUTH)
if response.status_code == 200:
data: dict = response.json()
for audit in data['audits']:
# make sure the audit event occurred after of the specified start time
specified_start_time = arrow.get(int(start_time))
audit_event_time = arrow.get(audit['created_at'])
if audit_event_time < specified_start_time:
continue
for audit_event in audit['events']:
if 'survey_response_id' in audit_event:
ticket['survey_response_id'] = audit_event['survey_response_id']
tickets_with_survey_response_ids.append(ticket)
break
else:
error = f'Failed to retrieve data, status code: {response.status_code}'
raise RuntimeError(error)
if not tickets_with_survey_response_ids:
print('No ticket received a CSAT survey response during this period.')
exit()
print(f'Added survey response ids to {len(tickets_with_survey_response_ids)} tickets.')
return tickets_with_survey_response_ids
def add_survey_responses(tickets: list) -> list:
"""
:param tickets: List of tickets with the survey response id included
:return: List of tickets with the survey responses included
"""
print('Adding the survey responses to the tickets...')
for ticket in tickets:
url = f'{ROOT}/guide/en-us/survey_responses/{ticket["survey_response_id"]}'
response = requests.get(url, auth=AUTH)
if response.status_code == 200:
data: dict = response.json()
ticket['survey_response'] = data['survey_response']
else:
error = f'- error getting response -> API responded with status {response.status_code}: {response.text}'
raise RuntimeError(error)
print(f'Added the survey responses to the tickets.')
return tickets
def list_survey_responses(tickets: list):
"""
:param tickets: List of tickets with the survey responses included
"""
for ticket in tickets:
print(f'\nTicket: https://{SUBDOMAIN}.zendesk.com/agent/tickets/{ticket["id"]}')
print(f'Subject: {ticket["subject"]}')
for answer in ticket['survey_response']['answers']:
print(f'CSAT survey answer: {answer}')
if __name__ == "__main__":
main()