For security purposes, modern browsers have a same-origin policy restriction that prevents scripts running in the browser from accessing resources in other domains. However, if the server in the other domain implements Cross-Origin Resource Sharing (CORS), the browser will allow a script to access resources in that domain.

Zendesk only implements CORS for API requests authenticated with OAuth access tokens. It does not implement CORS for API requests that use basic authentication (email and password) or a Zendesk API token.

Exceptions: A few Zendesk API endpoints don't require any authentication at all. They include the Create Request and Search Articles endpoints. CORS is implemented for these endpoints.

If an API request is authenticated with OAuth, Zendesk includes a special "Access-Control-Allow-Origin" CORS header in the response. The header has a value of '*', which allows requests from a page in any origin. The header basically gives the browser permission to access resources in the Zendesk domain.

The CORS headers are only included in the HTTP responses of certain API requests, including successful requests (HTTP statuses 200, 201, or 204) or if the resource wasn't found (status 404). The headers are not included in responses with a "403 Forbidden" or "429 Too Many Requests" status. In these cases, the browser detects a cross-origin error and blocks access to the Zendesk domain. The status of the request doesn't reach the browser.

The following snippet makes a cross-origin, browser-side API request with OAuth authentication:

var request = new XMLHttpRequest();var url = 'https://your_subdomain.zendesk.com/api/v2/tickets/' + ticket_id + '.json';request.open('GET', url, true);request.setRequestHeader("Authorization", "Bearer " + access_token);request.send();

This rest of this article expands this example.

Tutorial - Building a ticket details page

The web page developed in this article allows the user to fetch and display information about a Support ticket using only a browser. To keep things simple, it makes a GET request to the Show Ticket endpoint. The user enters the ticket id in an HTML form.

As per the API docs, the user must be an agent or admin in Zendesk Support to use the Show Ticket endpoint. (An admin has all the permissions of an agent.)

The restriction also means the web developer has to be an agent or an admin to test the page during development.

Developing the page consists of the following tasks:

To download the completed project files, click ticket_details.zip.

Disclaimer: Zendesk provides this article for instructional purposes only. Zendesk does not support or guarantee the code. Zendesk also can't provide support for third-party technologies such as JavaScript. Please post any issue in the comments section or search for a solution online.

Create an OAuth client in Zendesk Support

To use OAuth authentication, you must first register your app by creating an OAuth client in Zendesk Support.

You must be an admin to create an OAuth client.

To create an OAuth client

  1. In Admin Center, click the Apps and integrations icon () in the sidebar, then select APIs > Zendesk API > OAuth Clients.

  2. Click the Plus (+) icon on the right side of the page.

  3. Complete the form. See Registering your application with Zendesk for details.

  4. For the Redirect URLs field, enter the URL of the web page you'll build in this tutorial. Looking ahead, the file will be named ticket_details.html.

    If you have access to a web server, enter the full url to the future file:

    https://www.example.com/my_site/ticket_details.html

    If you don't have access to a web server, you can install and run a local web server such as XAMPP on your computer. Specify the localhost url as a redirect url. Example:

    http://localhost/my_site/ticket_details.html

    For more information on XAMPP and to download it, see the Apache Friends website.

Design the page layout

The tutorial consists of a simple HTML file, along with a CSS and a JavaScript file.

To design the page layout

  1. Create a text file named ticket_details.html.

  2. Add the following HTML to the file, then save it:

    <!DOCTYPE html><html><head>  <meta charset="utf-8" />  <title>Get ticket details</title>  <link rel="stylesheet" href="styles.css"></head>
    <body><h4>Enter a ticket id</h4><form id="get-ticket">  <input type="text" id="ticket-id" placeholder="ticket id" />  <button id="get-btn">Get ticket!</button> </form>
    <div id="error-msg"></div>
    <div id="details"></div>
    <script src="scripts.js"></script>
    </body></html>

    The page design consists of 3 display elements:

    • an HTML form to get a ticket id from the user
    • a div tag to display the ticket information
    • a div tag to display any error messages

    If you're satisfied with the layout, you can close the HTML file. You won't touch it again in this tutorial.

  3. Create a text file named styles.css in the same folder, then add the following rules to the file and save it:

    body {  font-family: Sans-Serif;  font-size: 85%;  margin: 20px;}#details {  display: none;  margin-top: 40px;}#details p {  line-height: 150%;}#error-msg {  display: none;  margin-top: 40px;}

    The "error-msg" and "details" divs are hidden by default with the display: none; setting. The script only displays each div when needed.

    Unless you want to tweak the styles, you can close the CSS file. You won't touch it again in this tutorial.

  4. Create a text file named scripts.js in the same folder, then add the following JavaScript to the file:

    function init() {  // reset page  document.getElementById('error-msg').style.display = "none";  document.getElementById('details').style.display = "none";
      // function body}
    function getTicket(event) {    // function body}
    window.addEventListener('load', init, false);document.getElementById('get-btn').addEventListener('click', getTicket, false);

    The script adds two event listeners. The first listens for a page to load. If detected, it runs the init() function. The second listens for a form button click. If detected, it runs the getTicket() function.

    The admittedly utilitarian layout should looks as follows in a browser:

    After the user requests a ticket:

  5. Save all 3 files, ticket_details.html, styles.css, and scripts.js, to the folder to be served by your web server. Make sure the url for ticket_details.html is the same as the redirect url you specified in your OAuth client. See Create an OAuth client in Zendesk Support.

Get the ticket id from the user

You must use JavaScript rather than an HTML form submission to get the ticket id the user submitted. The script needs the id to make an AJAX request to the Zendesk API.

To get the ticket id from the user

  • In the scripts.js file, replace the "// function body" comment in the getTicket() function with the following snippet:

    event.preventDefault();document.getElementById('error-msg').style.display = "none";  // clear error messagesvar ticket_id = document.getElementById('ticket-id').value;if ((!ticket_id) || isNaN(ticket_id)) {  showError('Oops, the field value should be a ticket id.');  return;}// check for token in local storage

How it works

The script performs the following actions:

  1. Hides the error-msg div in case the user's previous attempt to submit the id resulted in a form validation error message.

  2. Assigns the ticket id to the ticket_id variable.

  3. Performs basic validation on the field's value to make sure the user entered a value and that the value is a number.

  4. Displays any validation error and ends the script.

    The showError() function is a custom helper function you'll add later. It adds the error message to the error-msg div, then makes the div visible.

Check for an existing access token

Before making the API request with the ticket id, you should check to see if the user already has an OAuth access token. Later in the tutorial, you'll update the script to get an access token and then add it as a data item to the browser's localStorage object.

To check for an existing token

  • In the getTicket() function, replace the "// check for token in local storage" comment with the following snippet:

    if (localStorage.getItem('zauth')) {  var access_token = localStorage.getItem('zauth');  makeRequest(access_token, ticket_id);} else {  localStorage.setItem('ticket_id', ticket_id);  startAuthFlow();}

How it works

The script performs the following actions:

  1. Checks for the token in the browser's local storage:

    if (localStorage.getItem('zauth')) {  ...}

    The token in storage, if it exists, will be named zauth. You'll name it later in the tutorial.

  2. If it finds the token, it uses it to make the request.

    The makeRequest() is defined later in the tutorial.

  3. If it doesn't find the token, it saves the ticket id to restore the form's state after returning from the Zendesk authorization page:

    localStorage.setItem('ticket_id', ticket_id);

    Then it kicks off the authorization flow. The startAuthFlow() function is defined in the next section.

At this point, the script execution splits into two branches:

  • API request branch - The script uses the existing token to make the API request and display the results:

    makeRequest(access_token, ticket_id);
  • OAuth branch - The script starts the auth flow, gets the token from Zendesk, then rejoins the API request branch:

    startAuthFlow();

The tutorial follows the OAuth branch, which rejoins the API request branch.

Start the authorization flow

The implicit grant flow is used to get an OAuth access token for browser-side API requests.

The implicit grant flow is similar to the more common authorization code grant flow, except that you request a token directly instead of an authorization code. If the end user grants your app access, the token is sent immediately in the redirect URL. For more information, see Implicit grant flow in the Support Help Center.

To start the authorization flow

  1. Add the following function after the getTicket() function:

    function startAuthFlow() {  var endpoint = 'https://your_subdomain.zendesk.com/oauth/authorizations/new';  var url_params = '?' +  'response_type=token' + '&' +  'redirect_uri=your_redirect_url' + '&' +  'client_id=your_unique_identifier' + '&' +  'scope=' + encodeURIComponent('read write');  window.location = endpoint + url_params;}
  2. Specify your Zendesk subdomain in the endpoint.

  3. Specify your redirect_uri and client_id values, which are defined in the OAuth client you created earlier.

    Important

    • The redirect_uri value must match one of the Redirect URLs in the client UI.
    • The client_id is named Unique Identifier in the client UI.

How it works

The function builds a url with the required OAuth parameters. It then uses the url to open the Zendesk authorization page in the user's browser.

Handle the redirect

After the user grants access to your app, Zendesk uses your redirect uri to reopen the ticket_details.html page. Zendesk includes the access token as a url parameter:

{redirect_url}#access_token=f27851bd085c45ad9027a2ee223bde4f9a07171&scope=read+write&token_type=bearer

You can listen for page loads to handle the redirect. If a loaded page is ticket_details.html and the url contains "access_token", the script should extract the token and use it to make the API request.

The existing init() function in your script runs every time a page loads. It's a good place to check the url of each loaded page for the presence of an access token.

To handle the redirect

  1. In the init() function, replace the "// function body" comment with the following snippet:

    var url = window.location.href;if (url.indexOf('your_redirect_url') !== -1) {  if (url.indexOf('access_token=') !== -1) {    var access_token = readUrlParam(url, 'access_token');    localStorage.setItem('zauth', access_token);    var ticket_id = localStorage.getItem('ticket_id');    document.getElementById('ticket-id').value = ticket_id;    window.location.hash = "";    makeRequest(access_token, ticket_id);  }
      if (url.indexOf('error=') !== -1) {    var error_desc = readUrlParam(url, 'error_description');    var msg = 'Authorization error: ' + error_desc;    showError(msg);  }}
  2. Replace the your_redirect_url placeholder with your own url.

How it works

The script performs the following tasks:

  1. Gets the redirected page's full url:

    var url = window.location.href;
  2. Makes sure the page url matches the redirect url you provided Zendesk.

    if (url.indexOf('http://localhost/testing/cors/ticket_details.html') !== -1) {  ...}

    The script ignores every other page that might be loaded in the browser.

    Note: The JavaScript indexOf() method returns -1 if the string isn't found.

  3. Checks if the string "access_token=" is in the url:

    if (url.indexOf('access_token=') !== -1) {  ...}
  4. If the string is found, gets the token's value from the url and stores it for reuse:

    var access_token = readUrlParam(url, 'access_token');localStorage.setItem('zauth', access_token);

    The readUrlParam() function is a custom helper function you'll add later. It extracts the value of the specified url parameter and returns it.

  5. Retrieves the stored ticket id and restores the form's state.

  6. Clears the url parameters from the browser's address window so the access token isn't quite so exposed:

    window.location.hash = "";
  7. Makes the request by calling the makeRequest() function, which you'll add in the next section.

  8. If string "error=" is in the url, display the error message.

    If the user decides not to grant access to the application, the redirect URL will contain error and error_description parameters informing the script of the fact.

Make the API request

The final step is to make the API request with the access token. If the script execution reached this point, the token should be stored in the browser's local storage.

To make the API request

  1. Add the following function to the scripts.js file:

    function makeRequest(token, ticket_id) {  var request = new XMLHttpRequest();
      request.onreadystatechange = function() {    if (request.readyState === 4) {      if (request.status === 200) {        var data = JSON.parse(request.responseText);        var ticket = data.ticket;        var details_html =          '<p>' +          'Subject: ' + ticket.subject + '<br/>' +          'Status: <strong>' + ticket.status.toUpperCase() + '</strong><br/>' +          'Created: ' + ticket.created_at +          '</p>';        document.getElementById('details').innerHTML = details_html;        document.getElementById('details').style.display = "inherit";      } else {        document.getElementById('details').style.display = "none";        if (request.status === 0) {          showError('There was a problem with the request. Make sure you\'re an agent or admin in Zendesk Support.');        } else {          showError('Oops, the request returned \"' + request.status + ' ' + request.statusText + '\".');        }      }    }  };
      var url = 'https://your_subdomain.zendesk.com/api/v2/tickets/' + ticket_id + '.json';  request.open('GET', url, true);  request.setRequestHeader("Authorization", "Bearer " + token);  request.send();}
  2. Replace the your_subdomain placeholder in the url variable assignment near the end of the function.

How it works

The first statement creates a JavaScript request object:

var request = new XMLHttpRequest();

The rest of the function consists of 2 parts:

  • a callback function to run after getting a response from the server
  • the API request itself

The callback function is defined first even if it runs after the request is made. The script performs the following tasks:

  1. Defines the function:

    request.onreadystatechange = function() {  ...}

    onreadystatechange is an event handler that's called when the readyState attribute changes. (See the next line.) The script runs the callback function every time the attribute changes.

  2. Checks the readyState attribute to see if the request is done:

    if (request.readyState === 4) {  ...}

    A readyState value of 4 indicates the operation is complete. See the readyState docs on the Mozilla Developer Network.

  3. Checks that the API request was successful:

    if (request.status === 200) {  ...}

    An HTTP status of 200 means the GET API request was successful. The value is normally 200 for GET and PUT requests, 201 for POST requests, and 204 for DELETE requests. See the Zendesk API docs.

  4. Converts the data from a JSON string into a JavaScript object representing the ticket:

    var data = JSON.parse(request.responseText);var ticket = data.ticket;
  5. Builds an HTML string to display the ticket information:

    var details_html =  '<p>' +  'Subject: ' + ticket.subject + '<br/>' +  ...
  6. Inserts the HTML in the details div and makes the div visible.

  7. If the request was not successful (if anything but status 200 is returned), checks for a possible CORS error. CORS headers are not included in certain HTTP responses, including responses with a "403 Forbidden" or "429 Too Many Requests" status. In these cases, the browser detects a cross-origin error and blocks access to the Zendesk domain. The status of the request never reaches the browser.

    There's no easy way to check for a CORS error with JavaScript, but the HTTP status returned when one occurs is 0. Be aware that a value of 0 doesn't guarantee the problem was a CORS error.

  8. If the status code is not 200 or 0, displays the status code and error description, such as "404 Not found."

The code that makes the API request performs the following tasks.

  1. Builds the API endpoint url with the ticket id:

    var url = 'https://your_subdomain.zendesk.com/api/v2/tickets/' + ticket_id + '.json';

    See Show Tickets in the API docs for details.

  2. Configures the request:

    request.open('GET', url, true);
  3. Sets the Authorization header required for cross-origin API requests:

    request.setRequestHeader("Authorization", "Bearer " + token);
  4. Sends the request:

    request.send();

Add helper functions

The script uses a couple of custom helper functions for repetitive tasks. Add the two functions that follows to your scripts.js file.

function showError(msg) {  document.getElementById('error-msg').innerHTML = '<p> ' +  msg +  '</p>';  document.getElementById('error-msg').style.display = "inherit";}
function readUrlParam(url, param) {  param += '=';  if (url.indexOf(param) !== -1) {    var start = url.indexOf(param) + param.length;    var value = url.substr(start);    if (value.indexOf('&') !== -1) {      var end = value.indexOf('&');      value = value.substring(0, end);    }      return value;    } else {      return false;    }}

How it works

The showError() helper is self-explanatory.

Even if the readUrlParam() function consists of basic JavaScript string manipulation, it might need more explanation. The function performs the following tasks:

  1. Checks that the value of param exists in url before trying to get its value:

    if (url.indexOf(param) !== -1) {
  2. If the param string is found, takes the index of the string's first character, then shifts right by the param string's length to get the index of the value's first character:

    var start = url.indexOf(param) + param.length;
  3. Gets the string starting from the value's first character:

    var value = url.substr(start);
  4. Checks for other parameters in the extracted string by looking for the "&" character:

    if (value.indexOf('&') !== -1) {
  5. If found, finds the position of the first "&" character:

    var end = value.indexOf('&');
  6. Gets the substring from the start up to the "&" character's position:

    value = value.substring(0, end);

    At this point, only the parameter value should remain.

Code complete

Save or upload scripts.js to the folder to be served by your web server. It should be in the same folder as ticket_details.html. Make sure the url for ticket_details.html is the same as the redirect url you specified in your OAuth client. See Create an OAuth client in Zendesk Support.

Join the discussion about this article in the community.