Making secured API requests from a help center
On Enterprise plans, Zendesk help center themes can include client-side JavaScript that makes REST API calls and other HTTP requests from the browser. For example, your theme may include a custom button that makes requests to the Zendesk Ticketing API. As another example, your theme may include a custom page that accesses user-specific data from your back-end service.
At minimum, users must be signed in to the help center for the secured API requests to work. Additional requirements are described in this article.
Zendesk provides security tokens to verify client-side requests from the help center and ensure they're legitimate. The tokens used depend on whether you're sending the request to a Zendesk API or a third-party server:
Making Zendesk API requests with CSRF tokens
When signed in to a help center, users can authenticate client-side Zendesk API requests from the help center using their browser's Zendesk session cookie. As long as the signed-in user has the necessary role permission to the endpoint, no authentication credentials are required for GET requests.
However, requests to create, update, or delete Zendesk data also require a Cross-Site Request Forgery (CSRF) token for the user. This CSRF token helps Zendesk ensure the request originated from the user and the help center.
For example, if a user clicks a button that triggers a client-side Update Request request, the request must include a CSRF token. If the button instead triggers a Show Request request, a CSRF token isn't required.
Getting a CSRF token
You can retrieve the current user's CSRF token using a Show Self request.
fetch("/api/v2/users/me.json").then(response => {
console.log(response.json())
})
The response includes the CSRF token in the authenticity_token
property.
{
"user": {
"id": 1905826600027,
"url": "https://yoursubdomain.zendesk.com/api/v2/users/1905826600027.json",
"name": "John Doe",
...
"authenticity_token": "W/oOCh3/...",
...
}
}
Passing the CSRF token in Zendesk API requests
When making an applicable Zendesk API request from the help center, include the token in the X-CSRF-Token
HTTP header.
fetch("/api/v2/requests.json", {
method: "POST",
headers: {
"X-CSRF-Token": "W/oOCh3/...",
"Content-Type": "application/json"
},
body: JSON.stringify({
request: { subject: "Help!", comment: { body: "My printer is on fire!" } }
})
}).then(response => {
console.log(response.json())
})
Caching CSRF tokens
CSRF tokens provided by the Show Self endpoint last for the duration of the user session. To get a valid CSRF token for a new user session, send another Show Self request.
The Show Self endpoint is subject to API rate limits. Zendesk recommends caching CSRF tokens using the browser's local storage. Refresh the cache at set intervals, such as every two minutes.
Making third-party API requests with help center JWTs
On Enterprise plans, you can make help center requests to third-party servers. When doing so, you can include a signed help center JSON Web Token (JWT) in the request to connect to the third-party server.
The server that receives the request can then use a related JSON Web Key (JWK) to verify the JWT's signature. This verification ensures the request is legitimate and originated from the help center.
For example, you may set up middleware to receive requests from your help center. The middleware verifies each request's help center JWT to ensure the request is legitimate. If the request is legitimate, the middleware passes it on to your back-end service.
The help center JWT's payload includes the user's Zendesk email address, Zendesk user id, and external user id. The server receiving the JWT can use these identifiers to access and return related data for the user.
For example, your help center can send requests with help center JWTs to your online store's back-end API. Using identifiers from the JWT, you could fetch the current user's order history and display it on a custom page of the help center.
For an example of this setup, see Tutorial: Building a help center integration.
Getting the help center JWT
You can retrieve the current user's help center JWT by making a New Token request from the help center.
fetch("/api/v2/help_center/integration/token.json").then(response => {
console.log(response.json())
})
Because the help center makes a GET request to the Zendesk API to get the token, the user must be signed in to the help center for the request to succeed.
The response includes the JWT in the token
property.
{
"token": "eyJ0eXAi..."
}
JWT structure
Help center JWTs consist of three base64Url-encoded parts, separated by dots (.
):
Header
When decoded, the JWT's header is a JSON object describing the token and its format. The header contains the following fields.
Field | Description |
---|---|
alg | Algorithm used for the signature. Value is "RS256" |
typ | Token type. Value is "jwt" |
kid | Unique id for the JWT's signing key |
Payload
When decoded, the JWT's payload is a JSON object containing claims. For help center JWTs, these claims contain information about the request's origin and the token's expiration time.
The JWT's payload contains the following fields.
Field | Description |
---|---|
iss | Entity that issued the JWT. Value is "Zendesk" |
userId | Zendesk user id for the user that made the request |
Primary email address for the user that made the request | |
externalId | External id for the user that made the request. If the user has no external id, the value is null |
origin | Hostname that originated the request. Value is "https://{YOUR_SUBDOMAIN}.zendesk.com" |
exp | Expiration time for the JWT as Unix timestamp. Help center JWTs expire three minutes after creation |
Signature
The signature is an RS256-signed string that contains the header, payload, and a secret signing key. A server receiving the JWT can use a related JWK to verify the signature.
Passing the JWT in third-party API requests
The request property used to pass a help center JWT varies based on the server receiving the request. For example, you can pass the JWT as a bearer token in the Authorization
header.
const jwt = "YOUR_HC_JWT"
fetch("https://api.example.com/1", {
method: "DELETE",
headers: {
Authorization: `Bearer ${jwt}`
}
}).then(response => {
console.log(response.json())
})
Handling help center requests on your server
To receive requests from a help center, your server must enable
Cross-Origin Resource Sharing (CORS). The server's Access-Control-Allow-Origin response header must allow requests from the help center's hostname, such as "https
Getting a matching JWK
To verify help center JWTs, your server needs a matching JSON web key (JWK). You can use the List Public Keys endpoint to retrieve a list of valid JWKs for a Zendesk account.
curl -v https://{subdomain}.zendesk.com/api/v2/help_center/integration/keys.json
The response contains a keys
array containing JWK objects. Use the kid
in the JWT's header to find the matching JWK object.
{
"keys": [
{
"kty": "RSA",
"n": "yaiTIW...",
"e": "...",
"kid": "dee..."
},
...
]
}
The JWK contains the components needed to construct the public signing key for the matching JWT's signature.
Verifying the JWT with the JWK
Use a client library to decode and verify the JWT using the JWK. A list of popular JWT libraries is available at jwt.io. This guide provides examples for the following languages:
Node.js
The following Node.js JavaScript snippet uses the jsonwebtoken and jwks-rsa libraries to:
- Retrieve JWK objects from the List Public Keys endpoint
- Find a matching JWK for the JWT using its
kid
- Get a public signing key for the JWT's signature
- Verify the JWT's signature and expiration
- Decode the JWT's payload
const jwt = require("jsonwebtoken")
const jwksClient = require("jwks-rsa")
const ZENDESK_SUBDOMAIN = "YOUR_ZENDESK_SUBDOMAIN"
// Be sure to create a single instance of the jwksClient and reuse it, to leverage the caching mechanism
const jwksClientInstance = jwksClient({
jwksUri: `https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/help_center/integration/keys.json`
});
// JWT to verify. In production, retrieve this from the incoming request
const token = "eyJ0eXAi..."
const verifyToken = async token => {
const publicKey = await getPublicKeyFromJwks(
jwt.decode(token, { complete: true })
)
return jwt.verify(token, publicKey)
}
const getPublicKeyFromJwks = async decodedToken => {
const kid = decodedToken.header.kid
const signingKey = await jwksClientInstance.getSigningKey(kid)
return signingKey.rsaPublicKey
}
verifyToken(token).then(token => console.log(token))
Python
The following Python snippet uses the PyJWT and Requests libraries to:
- Retrieve JWK objects from the List Public Keys endpoint
- Find a matching JWK for the JWT using its
kid
- Get a public signing key for the JWT's signature
- Verify the JWT's signature and expiration
- Decode the JWT's payload
import json
import jwt
import requests
ZENDESK_SUBDOMAIN = "YOUR_ZENDESK_SUBDOMAIN"
# JWT to verify. In production, retrieve this from the incoming request
const token = "eyJ0eXAi..."
def verify_token(token):
public_keys = get_public_keys_from_jwks()
decoded_header = jwt.get_unverified_header(token)
kid = decoded_header["kid"]
key = public_keys[kid]
return jwt.decode(token, key, algorithms=["RS256"])
def get_public_keys_from_jwks():
jwks_url = f"https://{ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/help_center/integration/keys.json"
jwks_response = requests.get(jwks_url)
jwks_data = jwks_response.json()
public_keys = {}
for jwk in jwks_data["keys"]:
kid = jwk["kid"]
public_keys[kid] = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk))
return public_keys
print(verify_token(token))
Caching JWTs and JWKs
JWTs provided by the New Token endpoint expire three minutes after creation. After expiration, you can refresh the token by sending another New Token request. JWKs provided by the List Public Keys endpoint may also be periodically rotated.
Zendesk recommends caching JWTs using the browser's local storage. Refresh the cache at set intervals, such as every two minutes. Zendesk also recommends caching JWKs on your server. Many client libraries for JWKs support caching with options for refreshing.