Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with Zendesk

To integrate Replit with Zendesk, generate an API token from your Zendesk account settings, store it in Replit Secrets (lock icon πŸ”’), and call the Zendesk Support API from your Python or Node.js backend to create tickets, manage users, update ticket fields, and receive real-time webhook events. Deploy with Autoscale for webhook endpoints or Reserved VM for scheduled ticket processing jobs.

What you'll learn

  • How to generate a Zendesk API token and configure credentials in Replit Secrets
  • How to create, update, and search support tickets via the Zendesk API in Python and Node.js
  • How to manage Zendesk users and organizations programmatically
  • How to register Zendesk webhooks and process real-time ticket events in your Replit server
  • How to build automated ticket routing and SLA monitoring workflows from Replit
Book a free consultation
4.9Clutch rating ⭐
600+Happy partners
17+Countries served
190+Team members
Intermediate14 min read25 minutesCommunicationMarch 2026RapidDev Engineering Team
TL;DR

To integrate Replit with Zendesk, generate an API token from your Zendesk account settings, store it in Replit Secrets (lock icon πŸ”’), and call the Zendesk Support API from your Python or Node.js backend to create tickets, manage users, update ticket fields, and receive real-time webhook events. Deploy with Autoscale for webhook endpoints or Reserved VM for scheduled ticket processing jobs.

Why Connect Replit to Zendesk?

Zendesk is the dominant customer support platform for small to enterprise businesses, and its comprehensive REST API makes it a powerful integration point for automation. Connecting Replit to Zendesk enables you to build custom workflows that extend Zendesk's native capabilities: auto-creating tickets from application events, enriching tickets with data from external systems, triggering escalations based on custom business logic, or building internal dashboards that pull ticket metrics into your preferred reporting tool.

The most common use cases involve bidirectional data flow. Your application creates Zendesk tickets when errors or user issues are detected, Zendesk webhooks notify your Replit server when ticket status changes so you can update your own database, or a Replit job processes tickets on a schedule to apply routing logic too complex for Zendesk's native automations.

Difference from Intercom: Zendesk is ticket-based with SLA tracking, formal escalation paths, and organization-level management. Intercom is conversation-first, optimized for real-time chat with product-led engagement. Zendesk's API reflects this structure: tickets have stages, assignees, priorities, and custom fields that map well to formal support process automation. Store Zendesk credentials in Replit Secrets (lock icon πŸ”’ in the sidebar) β€” the API token grants full access to your support data.

Integration method

Standard API Integration

You connect Replit to Zendesk by generating an API token from your Zendesk account's Admin Center, storing it with your email in Replit Secrets, and calling the Zendesk Support REST API from your server-side Python or Node.js code. The API supports creating and updating tickets, managing users and organizations, searching ticket queues, and receiving real-time webhook events when tickets change. Authentication uses HTTP Basic Auth with email/token credentials.

Prerequisites

  • A Replit account with a Python or Node.js project created
  • A Zendesk account (Support Team plan or higher for API token access)
  • Admin access to your Zendesk instance to generate API tokens and configure webhooks
  • Your Zendesk subdomain (the part before .zendesk.com in your URL, e.g., 'yourcompany')
  • Node.js 18+ or Python 3.10+ (both available on Replit by default)

Step-by-step guide

1

Generate a Zendesk API Token

Log in to your Zendesk instance and click the Admin Center icon (the cog or grid icon) in the left sidebar. Navigate to Apps and Integrations > APIs > Zendesk API. On the Settings tab, ensure 'Token Access' is enabled (toggle it on if it is off). Then click the 'Add API token' button. Enter a description like 'Replit Integration' and click 'Create'. Zendesk will display the token once β€” copy it immediately before closing the dialog. The token looks like a long alphanumeric string starting with a number like 6wiIBWbGkBMo1jRiknWC0m... You will authenticate using your Zendesk admin email address and the API token together. The Basic Auth format is: {email}/token:{api_token}. For example, if your email is admin@company.com and your token is abc123, the username for Basic Auth is admin@company.com/token:abc123. Also note your Zendesk subdomain from the URL bar β€” it is the part before .zendesk.com. If your Zendesk URL is yourcompany.zendesk.com, your subdomain is 'yourcompany'. The API base URL uses this subdomain.

Pro tip: Create a dedicated Zendesk agent account for API integrations (e.g., api-integration@yourcompany.com) rather than using your personal admin account's token. This makes it easier to manage permissions and audit API usage, and prevents the integration from breaking if personal credentials change.

Expected result: You have a Zendesk API token, your admin email address, and your Zendesk subdomain copied and ready to add to Replit Secrets.

2

Store Zendesk Credentials in Replit Secrets

Open your Replit project and click the lock icon πŸ”’ in the left sidebar to open the Secrets pane. Add the following secrets: - Key: ZENDESK_SUBDOMAIN β€” Value: your Zendesk subdomain (e.g., 'yourcompany', not the full URL) - Key: ZENDESK_EMAIL β€” Value: the email address of the Zendesk account that generated the token - Key: ZENDESK_API_TOKEN β€” Value: the API token you generated Click 'Add Secret' after each entry. The Base64-encoded credentials for Basic Auth will be constructed in code as: {email}/token:{api_token}. Access them in Python: os.environ['ZENDESK_SUBDOMAIN'] os.environ['ZENDESK_EMAIL'] os.environ['ZENDESK_API_TOKEN'] In Node.js: process.env.ZENDESK_SUBDOMAIN etc. Restart your Repl after adding secrets. Never hardcode these in source files β€” Zendesk API tokens grant broad access to all tickets, users, and organizations in your support system.

Pro tip: You can also use OAuth 2.0 for Zendesk API access if you are building a multi-tenant app where customers connect their own Zendesk accounts. For single-instance automation, API token authentication is simpler and sufficient.

Expected result: ZENDESK_SUBDOMAIN, ZENDESK_EMAIL, and ZENDESK_API_TOKEN appear in the Replit Secrets pane and are accessible as environment variables.

3

Create and Manage Tickets with Python

The Zendesk API base URL format is https://{subdomain}.zendesk.com/api/v2. Authentication uses HTTP Basic Auth where the username is email/token:{api_token} and the password is any string β€” by convention, you pass the same email/token string or just the token as the password. The most common pattern is requests.get(url, auth=(f'{email}/token:{token}', '')). Tickets are the core object in Zendesk. A ticket has a requester (the customer), an assignee (the agent), a subject, a description (the first comment), a priority, a status, and custom fields. Creating a ticket requires at minimum a subject and a comment body. The Python module below provides a complete Zendesk client with ticket creation, updates, search, and comment management. Install requests with pip install requests.

zendesk_client.py
1import os
2import requests
3from typing import Optional
4
5SUBDOMAIN = os.environ["ZENDESK_SUBDOMAIN"]
6EMAIL = os.environ["ZENDESK_EMAIL"]
7TOKEN = os.environ["ZENDESK_API_TOKEN"]
8BASE_URL = f"https://{SUBDOMAIN}.zendesk.com/api/v2"
9
10# Zendesk Basic Auth: email/token:api_token as username
11AUTH = (f"{EMAIL}/token:{TOKEN}", "")
12
13HEADERS = {
14 "Content-Type": "application/json",
15 "Accept": "application/json"
16}
17
18def create_ticket(
19 subject: str,
20 body: str,
21 requester_email: str,
22 requester_name: str = "",
23 priority: str = "normal", # low, normal, high, urgent
24 tags: list = None,
25 custom_fields: list = None
26) -> dict:
27 """
28 Create a new support ticket.
29 Returns the created ticket object.
30 """
31 payload = {
32 "ticket": {
33 "subject": subject,
34 "comment": {"body": body},
35 "requester": {
36 "email": requester_email,
37 "name": requester_name or requester_email
38 },
39 "priority": priority,
40 "tags": tags or [],
41 "custom_fields": custom_fields or []
42 }
43 }
44 response = requests.post(f"{BASE_URL}/tickets.json", json=payload, auth=AUTH, headers=HEADERS)
45 response.raise_for_status()
46 return response.json()['ticket']
47
48def get_ticket(ticket_id: int) -> dict:
49 """Retrieve a ticket by ID."""
50 response = requests.get(f"{BASE_URL}/tickets/{ticket_id}.json", auth=AUTH, headers=HEADERS)
51 response.raise_for_status()
52 return response.json()['ticket']
53
54def update_ticket(
55 ticket_id: int,
56 status: str = None,
57 priority: str = None,
58 assignee_email: str = None,
59 internal_note: str = None,
60 tags: list = None
61) -> dict:
62 """Update ticket fields and optionally add an internal note."""
63 ticket_update = {}
64 if status:
65 ticket_update['status'] = status # open, pending, hold, solved, closed
66 if priority:
67 ticket_update['priority'] = priority
68 if assignee_email:
69 ticket_update['assignee_email'] = assignee_email
70 if tags:
71 ticket_update['tags'] = tags
72 if internal_note:
73 ticket_update['comment'] = {
74 'body': internal_note,
75 'public': False # False = internal note, True = public reply
76 }
77 response = requests.put(
78 f"{BASE_URL}/tickets/{ticket_id}.json",
79 json={'ticket': ticket_update},
80 auth=AUTH,
81 headers=HEADERS
82 )
83 response.raise_for_status()
84 return response.json()['ticket']
85
86def search_tickets(query: str, sort_by: str = "created_at", sort_order: str = "desc") -> list:
87 """
88 Search tickets using Zendesk search syntax.
89 Examples:
90 - 'status:open priority:high'
91 - 'requester:user@example.com status:new'
92 - 'created>2026-03-01 status:open'
93 """
94 params = {
95 'query': f'type:ticket {query}',
96 'sort_by': sort_by,
97 'sort_order': sort_order
98 }
99 response = requests.get(f"{BASE_URL}/search.json", params=params, auth=AUTH, headers=HEADERS)
100 response.raise_for_status()
101 return response.json().get('results', [])
102
103def add_public_comment(ticket_id: int, comment: str) -> dict:
104 """Add a public reply to a ticket (visible to the requester)."""
105 return update_ticket(ticket_id, internal_note=None)
106 # Use update_ticket with public: True comment instead
107
108def bulk_update_tickets(ticket_ids: list, updates: dict) -> dict:
109 """
110 Update multiple tickets at once.
111 updates: {status, priority, assignee_email, tags, ...}
112 """
113 payload = {'ticket': updates, 'ticket_ids': ticket_ids}
114 response = requests.put(
115 f"{BASE_URL}/tickets/update_many.json",
116 json=payload,
117 auth=AUTH,
118 headers=HEADERS
119 )
120 response.raise_for_status()
121 return response.json()
122
123# Example usage
124if __name__ == "__main__":
125 # Create a test ticket
126 ticket = create_ticket(
127 subject="Payment processing error for order #12345",
128 body="Customer reported that their payment failed during checkout. Order ID: 12345. Error code: CARD_DECLINED.",
129 requester_email="customer@example.com",
130 requester_name="Jane Smith",
131 priority="high",
132 tags=["payment", "checkout", "urgent"]
133 )
134 print(f"Ticket created: #{ticket['id']} β€” {ticket['subject']}")
135
136 # Add an internal note
137 updated = update_ticket(
138 ticket['id'],
139 internal_note="Checked payment logs. Card was flagged by fraud detection. Forwarding to billing team."
140 )
141 print(f"Ticket updated: status={updated['status']}")
142
143 # Search for open high-priority tickets
144 urgent_tickets = search_tickets("status:open priority:high")
145 print(f"Open high-priority tickets: {len(urgent_tickets)}")

Pro tip: Use the Zendesk search API for ticket queries β€” it is far more powerful than listing endpoints and supports Zendesk's full search syntax including field matching, date ranges, and tag filtering. The query syntax is documented at developer.zendesk.com/api-reference/ticketing/ticket-management/search/.

Expected result: Running the Python script creates a test ticket, adds an internal note, and searches for open high-priority tickets, printing the results.

4

Build a Zendesk Webhook Receiver with Node.js

Zendesk can send HTTP POST requests to your Replit server when tickets are created, updated, or solved β€” enabling real-time integration with external systems. You configure webhooks in Zendesk Admin Center > Apps and Integrations > Webhooks, and then connect them to Zendesk Triggers or Automations that define when the webhook fires. The Node.js server below receives Zendesk webhook payloads, verifies requests using a webhook secret, and processes ticket events. It includes a helper to update the ticket with enrichment data from an external system β€” the key pattern for CRM-enriched ticket workflows. Deploy this server on Replit Autoscale and use the public URL as the webhook endpoint URL in Zendesk.

server.js
1const express = require('express');
2const axios = require('axios');
3const crypto = require('crypto');
4
5const app = express();
6app.use(express.json());
7
8const SUBDOMAIN = process.env.ZENDESK_SUBDOMAIN;
9const EMAIL = process.env.ZENDESK_EMAIL;
10const TOKEN = process.env.ZENDESK_API_TOKEN;
11const WEBHOOK_SECRET = process.env.ZENDESK_WEBHOOK_SECRET || '';
12const BASE_URL = `https://${SUBDOMAIN}.zendesk.com/api/v2`;
13
14// Zendesk uses email/token:{api_token} as Basic Auth username
15const authHeader = 'Basic ' + Buffer.from(`${EMAIL}/token:${TOKEN}`).toString('base64');
16const zdHeaders = {
17 'Authorization': authHeader,
18 'Content-Type': 'application/json',
19 'Accept': 'application/json'
20};
21
22function verifyWebhookSignature(req) {
23 if (!WEBHOOK_SECRET) return true; // Skip verification if no secret configured
24 const signature = req.headers['x-zendesk-webhook-signature'];
25 const timestamp = req.headers['x-zendesk-webhook-signature-timestamp'];
26 const body = JSON.stringify(req.body);
27 const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
28 hmac.update(timestamp + body);
29 const expected = hmac.digest('base64');
30 return signature === expected;
31}
32
33async function getTicket(ticketId) {
34 const res = await axios.get(`${BASE_URL}/tickets/${ticketId}.json`, { headers: zdHeaders });
35 return res.data.ticket;
36}
37
38async function addInternalNote(ticketId, note) {
39 const res = await axios.put(`${BASE_URL}/tickets/${ticketId}.json`, {
40 ticket: {
41 comment: { body: note, public: false }
42 }
43 }, { headers: zdHeaders });
44 return res.data.ticket;
45}
46
47async function updateTicketFields(ticketId, updates) {
48 const res = await axios.put(`${BASE_URL}/tickets/${ticketId}.json`, {
49 ticket: updates
50 }, { headers: zdHeaders });
51 return res.data.ticket;
52}
53
54// POST /webhook β€” receive Zendesk trigger events
55app.post('/webhook', async (req, res) => {
56 // Verify webhook signature
57 if (!verifyWebhookSignature(req)) {
58 console.error('Webhook signature verification failed');
59 return res.status(401).json({ error: 'Invalid signature' });
60 }
61
62 // Respond quickly to Zendesk (must respond within 5 seconds)
63 res.json({ received: true });
64
65 // Process the event asynchronously
66 const payload = req.body;
67 const ticketId = payload.ticket_id || payload.id;
68 const eventType = payload.event_type || 'update';
69
70 console.log(`Zendesk webhook: ${eventType} on ticket #${ticketId}`);
71
72 if (ticketId) {
73 try {
74 // Example: enrich ticket with CRM data on creation
75 if (eventType === 'create' || payload.status === 'new') {
76 const ticket = await getTicket(ticketId);
77 const requesterEmail = ticket.via?.source?.from?.address || '';
78
79 // Here you would look up the customer in your CRM
80 // const crmData = await lookupCustomer(requesterEmail);
81 const crmNote = `Customer lookup: ${requesterEmail}\nAccount tier: [fetched from CRM]\nContract value: [fetched from CRM]`;
82
83 await addInternalNote(ticketId, crmNote);
84 console.log(`Enriched ticket #${ticketId} with CRM data`);
85 }
86 } catch (err) {
87 console.error(`Failed to process webhook for ticket #${ticketId}:`, err.message);
88 }
89 }
90});
91
92// GET /tickets/search β€” search tickets
93app.get('/tickets/search', async (req, res) => {
94 const { q } = req.query;
95 if (!q) return res.status(400).json({ error: 'q query parameter required' });
96
97 try {
98 const response = await axios.get(`${BASE_URL}/search.json`, {
99 headers: zdHeaders,
100 params: { query: `type:ticket ${q}` }
101 });
102 res.json({
103 total: response.data.count,
104 tickets: response.data.results
105 });
106 } catch (error) {
107 res.status(500).json({ error: error.response?.data || error.message });
108 }
109});
110
111app.listen(3000, '0.0.0.0', () => {
112 console.log('Zendesk integration server running on port 3000');
113});

Pro tip: Zendesk webhook delivery times out after 5 seconds. Always respond to the webhook request immediately (as shown with res.json({received: true})) and process the event asynchronously in a separate function. Long-running operations should be queued rather than processed inline.

Expected result: POST /webhook receives Zendesk events, verifies the signature, responds immediately, and processes ticket enrichment asynchronously.

Common use cases

Automatic Ticket Creation on Application Error

When your application detects a critical error β€” a payment failure, an API integration timeout, or a user-reported issue β€” a Replit backend automatically creates a Zendesk ticket with the error details, user information, and relevant logs attached, ensuring the support team is notified without manual escalation.

Replit Prompt

Build a Flask endpoint that receives error webhook payloads from your application monitoring tool, creates a high-priority Zendesk ticket with the error message and user ID, and assigns it to the Level 2 support group using the Zendesk API with credentials from Replit Secrets.

Copy this prompt to try it in Replit

CRM-Enriched Ticket Handler

When Zendesk sends a webhook notification that a new ticket is created, a Replit server looks up the customer in the CRM using the ticket's email address, retrieves their account tier and contract details, and updates the Zendesk ticket with CRM data as internal notes and custom field values, helping agents respond with full context.

Replit Prompt

Write a Node.js Express endpoint that receives Zendesk new ticket webhooks, queries Salesforce for the customer's account tier using the email from the ticket, and updates the Zendesk ticket with the account information as an internal comment using the Zendesk API.

Copy this prompt to try it in Replit

SLA Breach Alert System

A Replit scheduled job runs every hour to query open Zendesk tickets nearing their SLA deadline, compares breach risk against current time, and sends Slack alerts with ticket URLs to the assigned agents and their managers β€” providing early warning before tickets actually breach SLA.

Replit Prompt

Create a Python script that queries Zendesk for all open tickets with due dates in the next two hours using the Zendesk search API, filters by priority level, and sends a formatted Slack notification with ticket IDs and customer names for each at-risk ticket.

Copy this prompt to try it in Replit

Troubleshooting

API returns 401 Unauthorized on all requests

Cause: The authentication credentials are incorrectly formatted. Zendesk requires the email address in the format email/token:{api_token} as the Basic Auth username, not just the API token alone.

Solution: Verify that the auth tuple is (f'{email}/token:{api_token}', '') β€” the literal string '/token:' must be between the email and the token. The password field should be an empty string.

typescript
1import base64
2
3# Python: requests library format
4AUTH = (f"{os.environ['ZENDESK_EMAIL']}/token:{os.environ['ZENDESK_API_TOKEN']}", "")
5
6# Node.js: manual Base64 format
7const auth = Buffer.from(
8 `${process.env.ZENDESK_EMAIL}/token:${process.env.ZENDESK_API_TOKEN}`
9).toString('base64');
10const authHeader = `Basic ${auth}`;

Ticket creation returns 422 Unprocessable Entity with 'requester email' error

Cause: The requester email is invalid, not formatted correctly, or a user with that email already exists with a different name causing a conflict.

Solution: Verify the requester email is a valid email format. If you are creating tickets for existing users, you can use requester: {email: ...} and Zendesk will match the existing user. For new users, provide both email and name.

typescript
1# Correct ticket requester format
2payload = {
3 "ticket": {
4 "subject": "Support request",
5 "comment": {"body": "Ticket description"},
6 "requester": {
7 "email": "customer@example.com",
8 "name": "Customer Name" # Required for new users
9 }
10 }
11}

Webhook events are not being received even though the webhook is configured in Zendesk

Cause: The webhook is not connected to a Trigger or Automation in Zendesk, the Replit server is not running (not deployed), or the webhook URL is the development URL rather than the deployed Autoscale URL.

Solution: Webhooks in Zendesk must be associated with a Trigger or Automation to fire. In Admin Center, go to Triggers or Automations and add an action to call your webhook. Ensure your Replit app is deployed (Autoscale) and use the production URL, not the development webview URL.

Search API returns no results despite visible tickets matching the query

Cause: The search query syntax is incorrect, or the 'type:ticket' prefix is missing. Zendesk's search endpoint searches all object types by default.

Solution: Always prefix search queries with 'type:ticket' to limit results to tickets. Verify the field names and values in your query match Zendesk's search syntax exactly (e.g., 'status:open' not 'status=open').

typescript
1# Correct: include type:ticket prefix
2params = {'query': 'type:ticket status:open priority:high'}
3# Wrong: missing type prefix (returns mixed object types)
4# params = {'query': 'status:open priority:high'}

Best practices

  • Always store ZENDESK_SUBDOMAIN, ZENDESK_EMAIL, and ZENDESK_API_TOKEN in Replit Secrets β€” the API token grants full access to all tickets and customer data
  • Use the email/token:{api_token} format for Basic Auth β€” this is Zendesk-specific and different from standard Bearer token authentication
  • Always add type:ticket prefix to search queries to avoid accidentally fetching users or organizations
  • Respond to webhook requests within 5 seconds and process events asynchronously to avoid Zendesk marking your webhook as failed
  • Use internal notes (public: false in the comment object) for adding machine-generated enrichment data to tickets rather than public replies that customers can see
  • Implement webhook signature verification using HMAC-SHA256 with the webhook secret to ensure events actually come from Zendesk
  • Use bulk ticket update endpoints when updating multiple tickets at once rather than looping through individual update calls to respect API rate limits
  • Deploy on Replit Autoscale for webhook endpoints and use Reserved VM for scheduled SLA monitoring jobs that query tickets on a fixed interval

Alternatives

Frequently asked questions

How do I connect Replit to Zendesk?

Generate an API token in Zendesk Admin Center > Apps and Integrations > Zendesk API, then add ZENDESK_SUBDOMAIN, ZENDESK_EMAIL, and ZENDESK_API_TOKEN to Replit Secrets (lock icon πŸ”’ in the sidebar). Authenticate using HTTP Basic Auth with the username format: email/token:{api_token}.

Does Zendesk require OAuth for API access?

Not for single-user or service account access. Zendesk API tokens provide a simpler alternative to OAuth 2.0 β€” generate a token in account settings and use it with your email for Basic Auth. OAuth 2.0 is available and recommended for multi-tenant apps where users connect their own Zendesk accounts.

How do I store my Zendesk API credentials securely in Replit?

Click the lock icon πŸ”’ in the Replit sidebar to open Secrets and add ZENDESK_EMAIL, ZENDESK_API_TOKEN, and ZENDESK_SUBDOMAIN as separate secrets. Access them in Python with os.environ and in Node.js with process.env. Never hardcode them in source files.

Can I create Zendesk tickets automatically from my Replit app?

Yes. POST to /api/v2/tickets.json with a JSON body containing the subject, comment body, requester email, and optional priority and tags. The ticket is immediately created and visible in your Zendesk agent interface. You can also set custom field values if your Zendesk instance uses them.

How do I receive real-time Zendesk ticket events in my Replit app?

In Zendesk Admin Center, create a Webhook with your Replit server's URL as the endpoint. Then create a Trigger or Automation that calls the webhook when specific events occur (e.g., ticket created, status changed to solved). Implement the POST endpoint in your Replit server to receive and process the JSON payload.

What deployment type should I use on Replit for Zendesk integrations?

Use Autoscale deployment for webhook receiver servers that need a stable public URL for Zendesk to call. Use Reserved VM for continuously running processes or scheduled jobs that monitor ticket queues, send SLA alerts, or run batch ticket updates on a regular schedule.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation β€” no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.