I tried relaying a fixed authentication external integration to OAuth using Zendesk ZIS

I tried relaying a fixed authentication external integration to OAuth using Zendesk ZIS

I tried a configuration where events are sent from an external service that can only use fixed Basic authentication to a ZIS inbound webhook, and then calls the Zendesk API using an OAuth connection. I will introduce the configuration and steps up to adding a private comment to a verification ticket.
2026.09.01

This page has been translated by machine translation. View original

Introduction

When migrating from Zendesk API tokens to OAuth, if the caller is a program you wrote yourself, you can obtain an access token using client_credentials and refresh it as it expires. However, some callers, such as SaaS products that only allow you to enter fixed credentials in an admin panel, cannot implement token acquisition logic.

Under these conditions, pasting a time-limited access token as a fixed value will cause the integration to stop after it expires. Therefore, I tried a configuration where requests from external services are received by a Zendesk Integration Services (ZIS) inbound webhook, and ZIS calls the Zendesk API using an OAuth connection managed by ZIS.

In this configuration, the access token of the Zendesk OAuth connection held by ZIS does not expire, and refresh tokens are not used. Rather than a configuration where ZIS renews the token each time it expires, it continuously uses the connection created when an administrator authenticated for the first time. Therefore, there is no need to implement token acquisition or renewal on the external service side.

As a result, I was able to send an event from an external source using fixed Basic authentication and add one private comment to a verification ticket. This article introduces the minimal configuration used for verification.

Target Audience

  • Those considering migrating from Zendesk API tokens to OAuth
  • Those who cannot implement OAuth token acquisition in the caller
  • Those who want to try combining ZIS inbound webhooks with OAuth connections

References

Configuration

For the external service, you configure the URL, username, and password returned when the inbound webhook is created. The external service only needs to send events using fixed Basic authentication. Upon receiving an event, ZIS starts the flow corresponding to the job spec and calls the Zendesk Support API using the OAuth connection.

For verification, I used a Zendesk test tenant and a new ticket with one initial comment. As the initial setup during migration, an admin API token was used to upload the bundle and job spec. In contrast, ticket updates after setup use the OAuth connection rather than an API token. The goal is to increase the number of private comments with a unique body from 0 before sending the event to 1 after sending it.

Obtaining the ZIS OAuth Token

Here, it is assumed that the ZIS integration has already been registered.

For ZIS_INTEGRATION, specify the name used when registering the integration, not an arbitrary string.

export ZENDESK_SUBDOMAIN='your_subdomain'
export ZENDESK_ADMIN_EMAIL='admin@example.com'
export ZENDESK_API_TOKEN='your_api_token'
export ZIS_INTEGRATION='your_zis_integration'
export TICKET_ID='12345'
export STAMP=$(date -u +%Y%m%d%H%M%S)

When you register a ZIS integration, an OAuth client with the identifier zis_${ZIS_INTEGRATION} is automatically generated.

get-zis-client.sh
zis_client_id=$(curl -sS \
  "https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/oauth/clients.json" \
  -u "${ZENDESK_ADMIN_EMAIL}/token:${ZENDESK_API_TOKEN}" \
  | jq -er --arg identifier "zis_${ZIS_INTEGRATION}" \
    '[.clients[] | select(.identifier == $identifier)]
     | if length == 1 then .[0].id else error("ZIS client count must be 1") end')

Using the obtained ID, issue an OAuth token to be used for calling the ZIS API.

create-zis-token.sh
token_response=$(curl -sS -X POST \
  "https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/oauth/tokens.json" \
  -u "${ZENDESK_ADMIN_EMAIL}/token:${ZENDESK_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{\"token\":{\"client_id\":${zis_client_id},\"scopes\":[\"read\",\"write\"]}}")

export ZIS_ACCESS_TOKEN=$(echo "$token_response" | jq -er '.token.full_token')
export ZIS_ACCESS_TOKEN_ID=$(echo "$token_response" | jq -er '.token.id')
unset token_response

Creating the OAuth Connection

Create an OAuth connection named zendesk.

First, call the OAuth start API.

create-connection.sh
response=$(curl -X POST \
  "https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/services/zis/connections/oauth/start/${ZIS_INTEGRATION}" \
  -H "Authorization: Bearer ${ZIS_ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"zendesk\",
    \"oauth_client_name\": \"zendesk\",
    \"oauth_url_subdomain\": \"${ZENDESK_SUBDOMAIN}\",
    \"origin_oauth_redirect_url\": \"https://example.local\",
    \"permission_scopes\": \"read write\",
    \"allow_offline_access\": false
  }")

echo "$response" | jq -r '.redirect_url'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   666    0   425  100   241    321    182  0:00:01  0:00:01 --:--:--   503
https://zis.zendesk.com/api/services/zis/connections/oauth/start_redirect?flow_token=ey****

Open the output URL in an administrator's browser and follow the on-screen instructions to authenticate as an administrator. Once authentication is complete, ZIS stores the OAuth access token as a connection and redirects the browser to https://example.local. Since example.local is a dummy URL for verification purposes, the browser will display a connection error.

zendeskoauth01

This is expected behavior. Rather than what is displayed in the browser, confirm that the zendesk connection was created using the following API.

show-connection.sh
curl \
  "https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/services/zis/connections/${ZIS_INTEGRATION}?name=zendesk" \
  -H "Authorization: Bearer ${ZIS_ACCESS_TOKEN}" \
  | jq '{name, permission_scope, token_type}'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   869    0   869    0     0   1712      0 --:--:-- --:--:-- --:--:--  1714
{
  "name": "zendesk",
  "permission_scope": "read write",
  "token_type": "bearer"
}

Uploading the Bundle

The bundle used this time consists of three resources: an Action that adds a private comment to a ticket, a Flow that calls that Action, and a JobSpec that links inbound webhook events to the Flow.

bundle.template.json
{
  "name": "ZIS inbound OAuth bridge __STAMP__",
  "description": "Add one private comment to a disposable verification ticket",
  "zis_template_version": "2019-10-14",
  "resources": {
    "blogcomment": {
      "type": "ZIS::Action::Http",
      "properties": {
        "name": "blogcomment",
        "definition": {
          "method": "PUT",
          "path": "/api/v2/tickets/__TICKET_ID__.json",
          "connectionName": "zendesk",
          "headers": [{"key": "Content-Type", "value": "application/json"}],
          "requestBody": {
            "ticket": {
              "comment": {"body.$": "$.comment_body", "public": false}
            }
          }
        }
      }
    },
    "blogflow": {
      "type": "ZIS::Flow",
      "properties": {
        "name": "blogflow",
        "definition": {
          "StartAt": "AddPrivateComment",
          "States": {
            "AddPrivateComment": {
              "Type": "Action",
              "ActionName": "zis:__INTEGRATION__:action:blogcomment",
              "Parameters": {"comment_body.$": "$.input.comment_body"},
              "End": true
            }
          }
        }
      }
    },
    "blogjob": {
      "type": "ZIS::JobSpec",
      "properties": {
        "name": "blogjob",
        "event_source": "blog_zis_e2e___STAMP__",
        "event_type": "comment_requested",
        "flow_name": "zis:__INTEGRATION__:flow:blogflow"
      }
    }
  }
}

Generate JSON with values substituted for the placeholders.

render-bundle.sh
jq \
  --arg stamp "$STAMP" \
  --arg ticket_id "$TICKET_ID" \
  --arg integration "$ZIS_INTEGRATION" \
  'walk(
    if type == "string" then
      gsub("__STAMP__"; $stamp)
      | gsub("__TICKET_ID__"; $ticket_id)
      | gsub("__INTEGRATION__"; $integration)
    else .
    end
  )' bundle.template.json > rendered-bundle.json

Upload the generated file and install the job spec.

deploy-bundle.sh
curl -X POST \
  "https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/services/zis/registry/${ZIS_INTEGRATION}/bundles" \
  -u "${ZENDESK_ADMIN_EMAIL}/token:${ZENDESK_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data-binary @rendered-bundle.json

curl -X POST \
  "https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/services/zis/registry/job_specs/install?job_spec_name=zis:${ZIS_INTEGRATION}:job_spec:blogjob" \
  -u "${ZENDESK_ADMIN_EMAIL}/token:${ZENDESK_API_TOKEN}"

Creating the Inbound Webhook

Specify the same event_source and event_type as in the job spec.

create-inbound-webhook.sh
curl -X POST \
  "https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/services/zis/inbound_webhooks/generic/${ZIS_INTEGRATION}" \
  -H "Authorization: Bearer ${ZIS_ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{
    \"source_system\": \"blog_zis_e2e_${STAMP}\",
    \"event_type\": \"comment_requested\"
  }"

Use the path, username, and password from the response to configure the external service.

Verification

Set the values returned at creation time as environment variables and send an event to the inbound webhook.

send-event.sh
export ZIS_WEBHOOK_PATH='response_path'
export ZIS_WEBHOOK_USERNAME='response_username'
export ZIS_WEBHOOK_PASSWORD='response_password'

curl -X POST \
  "https://${ZENDESK_SUBDOMAIN}.zendesk.com${ZIS_WEBHOOK_PATH}" \
  -u "${ZIS_WEBHOOK_USERNAME}:${ZIS_WEBHOOK_PASSWORD}" \
  -H "Content-Type: application/json" \
  -d "{\"comment_body\":\"ZIS_BLOG_E2E_${STAMP}\"}"

The POST returned 200. After checking the ticket's comment list 5 seconds later, one private comment with an exact match to the submitted body had been added.

Verification Item Result
POST to inbound webhook 200
Number of matches for unique body 0 to 1
Added comment ID 1
public of added comment false

From these results, it was confirmed that an external service, while using fixed Basic authentication, can call the Zendesk API using an OAuth connection internal to ZIS.

Summary

Even in cases where only fixed Basic authentication can be configured, it was possible to relay through a ZIS inbound webhook and call the Zendesk API via OAuth. Since the access token of the Zendesk OAuth connection does not expire, there is no need to implement token acquisition or renewal on the external service side. Note that in production environments, if the caller supports it, please prioritize OAuth or signed webhooks.


Zendeskの導入支援ならクラスメソッドへ

クラスメソッドはZendeskのライセンス販売パートナーです。Zendesk導入にあたって、設定代行、オペレーターや管理者向けのトレーニング、独自のアプリ開発、データ移行など、様々なサービスをご提供しております。既に導入済みのお客様に対してもご支援できますので、まずはお気軽にご相談ください。

Zendeskの導入支援の詳細を見る

Share this article