Requests

Zendesk apps can send or receive data by making HTTP requests to web apps, web services, and backend systems. The requests are made with AJAX. To learn more about AJAX, see Ajax (programming) on Wikipedia.

The framework supports GET, POST, PUT, and DELETE requests. It accepts JSON and XML data types.

As of September 2014, the framework no longer accepts JSONP requests. Zendesk recommends using CORS requests instead for making cross-domain requests.

Request basics

A request in a Zendesk app consists of two parts:

  • a request definition that contains all the information necessary to configure the request
  • a separate AJAX call that makes the request

Here's an example request definition:

  requests: {
    fetchHeartyQuote: {
      url: 'http://www.iheartquotes.com/api/v1/random',
      type: 'GET',
      dataType: 'json'
    },
    ...
  }

The fetchHeartyQuote object configures a GET request to send to the http://www.iheartquotes.com/api/random endpoint documented on the iheartquote.com website. It doesn't actually make a request; it just configures one. See Define a request below for more details.

The actual request is made with the framework's ajax() method. The method takes the request definition's name as an argument, in this case 'fetchHeartyQuote':

  ...
  this.ajax('fetchHeartyQuote');

See Make a request below for details.

You can watch for the outcome of the request with the .done, .fail, or .always request events. The events fire only after a request is complete and the server reports back on the request's success or failure. Example:

events: {
  'fetchHeartyQuote.done': function() {
    services.notify('Hearty quote received');
  },
  ...
},

Note: Requests made to the Zendesk API may be rate-limited. If a request is rate-limited the framework will auto-retry the request after a delay. When the request is rate-limited the framework will emit a .delayed event, with the length of the delay in seconds as an argument. To disable this behaviour and handle rate-limiting yourself, set the autoRetry option to false in your request options. For more information about rate limits, see Rate limits in the Zendesk REST API docs.

See Request events for details.

Define a request

A request definition contains all the information necessary to configure a jQuery AJAX request. A definition can be any of the following:

  • a string that's used as a URL for a GET request
  • an object that's compatible with jQuery.ajax
  • a function that returns either of the above

This section covers the following topics:

Defining the request

Specify your request definition as a property of the requests object in the app.js file. Example:

requests: {
  fetchBookmarks: {
    url: 'https://www.example.com/api/v2/bookmarks.json',
    type: 'GET',
    dataType: 'json'
  },
  ...
}

For details on the definition's properties, see Request definition reference below.

You can also specify a function that returns the definition. Using a function lets you pass parameters to the definition. Example:

  requests: {
    fetchBookmark: function(bookmark_id) {
      return {
        url: 'https://www.example.com/api/v2/bookmarks/' + bookmark_id + '.json',
        type: 'GET',
        dataType: 'json'
      };
    },
    ...
  }
Request definition reference

Objects that define requests can have the following properties:

Property Description
url The URL to send the request. See notes below
type HTTP method 'GET', 'POST', 'PUT', or 'DELETE'. Default is 'GET'
dataType Type of data expected back from the server. Typically 'json' or 'xml'
contentType Type of data expected by the server for POST or PUT requests. Typically 'application/json', 'application/xml', or 'application/x-www-form-urlencoded' (the default)
data Data to be sent to the server
headers An object of additional header key/value pairs to send with the request. See notes below
password A password for basic authentication
username A username for basic authentication
secure true or false. See Secure requests
cors true or false. See CORS requests

You can also use any setting that jQuery.ajax() supports. See jQuery.ajax reference on the jQuery website.

Notes

If you're sending a request to the Zendesk API, you can use a relative URL. Example: url: '/api/v2/tickets.json'.

If url is a fully qualified URL such as http://example.com/api/widget, the request is sent to a pass-through proxy. The proxy sends the request as is without interfering with the request or response headers.

If url is a fully qualified URL and you specify cors: true, the request is sent directly to the remote server. See CORS requests for details.

If you include custom headers in your request definition and you don't specify cors: true, then don't use underscores in the header names. The proxy blocks the headers to prevent naming ambiguities when mapping headers to CGI variables. Example:

  myDefinition: {
    url:      'http://repin.org/get',
    type:     'GET',
    dataType: 'json',
    headers:  { my_header: "allow"  // oops, blocked }
  },
Request definition examples

GET request with URL parameters:

  requests: {
    save: function() {
      return {
        url:  'http://www.example.com/abc?x=1&y=2',
        type: 'GET'
      };
    }
  }

GET request with data:

  requests: {
    save: function() {
      return {
        url:  'http://www.example.com/abc',
        type: 'GET',
        data: {x: 1, y: 2}
      };
    }
  }

POST request with form data:

  requests: {
    save: function() {
      return {
        url:  'http://www.example.com/post',
        type: 'POST',
        data: {x: 1, y: 2}
      };
    }
  }

POST request with JSON data (need to JSON stringify the data):

  requests: {
    save: function() {
      return {
        url:         'http://www.example.com/post.json',
        type:        'POST',
        contentType: 'application/json',
        data:        JSON.stringify({x: 1, y: 2}), // '{"x": 1, "y": 2}'
      };
    }
  }

POST request with XML data:

  requests: {
    save: function() {
      return {
        url:         'http://www.example.com/post.xml',
        type:        'POST',
        contentType: 'application/xml',
        data:        '1'
      };
    }
  }

Make a request

After defining a request, make the request with the following framework method:

this.ajax(definition[, *args])

Example:

  this.ajax('fetchTeachMyAPIUserById', this.updateUserId);

The ajax() method is similar to the jQuery.ajax() method. See jQuery.ajax on the jQuery website.

Arguments
  • definition the request definition
  • *args any additional arguments to pass to the request definition if it's a function
Returns

A jQuery promise that's resolved when the request is complete. Any done, fail, or always callback functions receive the same arguments as the corresponding callbacks for calls to jQuery.ajax(). For a successful request, this means the request receives the response body after it's been modified by any jQuery dataFilter functions.

Events

When the request is finished, the {request_name}.always and either the {request_name}.done or {request_name}.fail events are triggered. Any event handlers receive the same arguments as the promise callbacks.

Example
{
   requests: {
     getWidget: function(name) {
       return {
         url:      'http://example.com/widgets/' + name,
         dataType: 'json'
       };
     }
   },

   showWidget(name) {
     this.ajax('getWidget', name)
         .done(function(data) {
            render the JSON data
         });
   }
}

See also Request events.

CORS requests

You can make a CORS request by adding cors: true to the request definition:

{
   requests: {
     getWidget: function(name) {
       return {
         url:  'https://cdn.example.com/widgets/' + name,
         cors: true
       };
     }
   }
}

Cross-Origin Resource Sharing (CORS) lets a JavaScript client access resources in other domains. Historically, using JavaScript to access resources in domains other than the one from which the request originated was prohibited by browser-enforced, same-origin security policies. CORS solves the problem and allows resources to be shared across origins. The server must implement CORS and indicate that the domain of the client making the request is permitted to do so.

CORS is not just for external requests. You can use it to access internal resources that would otherwise be inaccessible. In a large enterprise, for example, resources can be distributed in subdomains or behind a firewall.

Note: By default, browsers don't send credentials with a cross-origin request. Credentials include cookies as well as HTTP authentication headers. To send credentials with a CORS request, your app must set withCredentials to true along with any other XMLHttpRequest fields via the xhrFields option in your request definition:

{
   requests: {
     getWidget: function(name) {
       return {
         url:  'https://cdn.example.com/widgets/' + name,
         cors: true,
         xhrFields: {
           withCredentials: true
         }
       };
     }
   }
}
Preflight requests

A preflight request is a preliminary request sent to the server before the actual request.

Preflight requests are not made for GET, POST, or HEAD requests. In these cases, simple request headers are all the server needs to determine whether or not to allow the request.

However, if you add custom headers to the request or if you specify another request method such as PUT, a preflight request is made to the server to check to see if it'll allow the request. If the server's response contains certain headers allowing the use of the request headers or method, then the actual request is made.

CORS browser support

CORS is supported by most modern browsers. Review the list of browsers that support CORS to determine if using CORS is right for your app.

Internet Explorer 9 limitations

Internet Explorer 9 has limited support for CORS. Limitations include:

  • The target URL must be accessed using the HTTP or HTTPS protocols
  • The target URL must be accessed using only the HTTP methods GET and POST
  • No custom headers may be added to the request
  • Only text/plain is supported for the request's Content-Type header
  • No authentication or cookies are sent with the request
  • Requests must be targeted to the same scheme as the hosting page

No additional work is required to support CORS for clients using IE9.

Example CORS request and response headers

If you make a GET request from https://example.zendesk.com, the following headers are sent to the server:

Accept:application/json, text/javascript, */*; q=0.01
Host:cdn.example.com
Origin:https://example.zendesk.com
Referer:https://example.zendesk.com/agent

Note: Some headers have been omitted.

If the server allows the request, it includes an Access-Control-Allow-Origin header in the response echoing back the originating domain, or '*' if it’s a public resource:

Access-Control-Allow-Credentials:true
Access-Control-Allow-Methods:GET, POST, OPTIONS
Access-Control-Allow-Origin:https://example.zendesk.com
Access-Control-Max-Age:3600
Content-Type:application/json;charset=utf-8

Secure requests

Support agents can view the details of AJAX requests made by an app in their browser console. You can hide sensitive information in the requests by defining the information as a secure setting in your manifest.json file and then replacing the setting value in the request definition with a placeholder and adding a secure: true property.

Testing and development

Note: Secure requests don't work when the app is running locally on the ZAT server. See ZAT server limitations to learn how to avoid attempting secure requests while using the ZAT server, or see below for instructions on making production-ready secure requests while using ZAT.

If you need to make real secure requests while testing your app using ZAT, the workaround linked above using isZatEnabled() is not sufficient. Instead you can upload the app to your account using zat create or the Apps > Manage page in the Zendesk Support admin interface, and configure an installation with valid settings. Make a note of the app installation's id by loading https://subdomain.zendesk.com/api/v2/apps/installations.json. You can now run zat server --app-id=INSTALLATION_ID to access your stored settings or use zat update to test new changes. The domainWhitelist of the most recently uploaded zip file will be in use, not the value from the manifest in zat server.

Server requirements

To successfully make secure requests, the responding server must:

  1. Allow requests from the IP address ranges documented in this article.
  2. Provide a valid and complete SSL certificate chain. For example, curl https://YOUR-SERVER/ won't fail with SSL errors using a standard set of root certificates such as the Mozilla/Debian bundle published in the Ubuntu packages.
Example

The following example secures an API key for the teachmyapi API. It prevents the actual key from being displayed as a URL parameter in a browser console.

First, the API key is defined as a parameter named key in the manifest.json file, and then secured with the "secure": true property.

  ...
  "parameters": [
    {
      "name": "key",
      "type": "text",
      "required": true,
      "secure": true
    },
   ...
  ],

Note: The value of the API key is not defined in the manifest file. The app asks for the value as a setting when the app is installed.

Second, in the app.js file, the setting is specified with the {{setting.key}} placeholder in the request definition, and then secured with secure: true:

{
  requests: {
    getFromTeachmyapi: {
      url: 'https://www.teachmyapi.com/api/{{setting.key}}/users',
      type: 'GET',
      dataType: 'json',
      secure: true,
      headers: {
        'X-Setting': '{{setting.key}}'
      }
    },
    ...
  }
}

The placeholder, not the value, is sent with the request. All an agent can see in the console is the placeholder. The actual value of the setting is inserted server-side at the proxy layer.

Note: Don't confuse the {{setting.name}} placeholder with the {{setting 'name'}} template helper or the setting('name') framework method for inserting setting values in the JavaScript.

See the Secure requests sample app on Github.

Securing credentials for basic authentication

You can't create username and password secure settings and then try (or have the framework try) to base64-encode the values in the request for basic authentication. Secure setting values can't be changed programmatically on the client side.

However, you could create a single secure setting for the base64-encoded value of the combined username and password:

  {
     "name": "credentials",
     "type": "text",
     "required": true,
     "secure": true
   },

When installing the app, the admin would have to enter the encoded value, such as "dXNlckBleGFtcGxlLmNvbTpwYXNTd29yZA==".

In the request, pass the setting in an authorization header:

  ...
  headers: { "Authorization": "Basic {{setting.credentials}}" },
  secure: true

JWT encoding

Zendesk apps support encoding information into JSON web tokens (JWT) and sending them in requests. JWT is a way of encoding and sending information in a HTTP request to be verified by a second party.

Note: Only characters that belong to the Latin-1 (ISO-8859-1) range are allowed. Non-UTF-8 headers will still be parsed as UTF-8.

To send a JWT token, start by specifying the information to send in an object in the request definition:

  jwt: {
    algorithm: 'HS256',
    secret_key: '{{setting.shared_secret}}',
    expiry: 3600, // one hour token expiry
    claims: {
      iss: '{{setting.subdomain}}'
    }
  }

The app encodes this information into a JWT token that might look like this (line breaks added for clarity):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLm9yZy
IsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUuY29tIiwiaWF0IjoxMzU2OTk5NTI0LCJuYmYiOjEzN
TcwMDAwMDAsImV4cCI6MTQwNzAxOTYyOSwianRpIjoiaWQxMjM0NTYiLCJ0eXAiOiJodHRwczpc
L1wvZXhhbXBsZS5jb21cL3JlZ2lzdGVyIiwidGVzdC10eXBlIjoiZm9vIn0.UGLFIRACaHpGGID
EEv-4IIdLfCGXT62X1vYx7keNMyc

The token consists of the header, the claims body, and the signature separated by periods.

The information is not encrypted. It's only digitally signed with the specified secret key. As a result, include only information that's not secret but needs to be verified by the receiving party, such as the names of Zendesk Support users. Don't include any sensitive information, such as passwords.

Reference the encoded JWT token in your code with the placeholder {{jwt.token}}:

  url: 'http://{{setting.subdomain}}.herokuapp.com/200/ok?token={{jwt.token}}',

You can also use the placeholder in the header or request body.

After the AJAX request is sent, the receiving party can decode the token using a JWT library and the shared secret key. See jwt.io.