To integrate Zendesk with Lovable, enable API token access in your Zendesk admin settings, store the token and subdomain in Cloud → Secrets, then build Edge Functions that proxy calls to the Zendesk Support API for tickets, users, and organizations. Zendesk is the market-leading support platform, ideal for building custom support portals and ticket management dashboards directly inside your Lovable app.
Build custom support portals and ticket workflows in Lovable with Zendesk's API
Zendesk is the enterprise standard for customer support platforms, trusted by companies of all sizes for managing customer interactions across email, chat, and social channels. For founders building Lovable apps for paying customers, integrating with Zendesk creates a seamless support experience: users submit requests directly in your app rather than opening a separate Zendesk portal, and your support team works tickets in the Zendesk UI they already know.
Zendesk's Support API v2 is REST-based and covers the full support workflow: tickets (creation, updates, comments, status), users (customers and agents), organizations (grouping customers), and search. Authentication uses HTTP Basic Auth with your Zendesk agent email and an API token — a simpler approach than OAuth2 that works perfectly for server-side Edge Function integrations. Your Zendesk subdomain (the 'yourcompany' in yourcompany.zendesk.com) is part of every API base URL.
The most powerful Lovable + Zendesk use case is replacing the generic Zendesk customer portal with a custom-branded experience embedded in your product. Instead of redirecting users to support.yourcompany.zendesk.com, you can render a ticket submission form and ticket history list directly in your Lovable app. Users see their open and resolved tickets without leaving your product, while your support team works the same tickets in their Zendesk agent workspace.
Another high-value pattern: create Zendesk tickets automatically from in-app error events, user feedback submissions, or triggered alerts — converting passive user signals into actionable support tickets without any manual copy-paste.
Integration method
Zendesk has no native Lovable connector. All support operations are proxied through Lovable Edge Functions that call the Zendesk Support API v2. API credentials (email, API token, and subdomain) are stored in Cloud → Secrets and accessed via Deno.env.get(), keeping authentication server-side and preventing CORS errors from direct browser calls to Zendesk's API.
Prerequisites
- A Lovable project with Lovable Cloud enabled (Edge Functions require a deployed app)
- A Zendesk account with at least the Support Team plan (API access requires a paid plan; Zendesk Suite plans all include API access)
- Admin access to your Zendesk account to enable API token authentication
- Your Zendesk subdomain (the 'yourcompany' part of yourcompany.zendesk.com)
Step-by-step guide
Enable API token access and generate a token in Zendesk Admin
Enable API token access and generate a token in Zendesk Admin
Zendesk supports two authentication methods for API access: OAuth2 and API token. For Lovable Edge Function integrations, API token authentication is simpler and more appropriate — it uses HTTP Basic Auth with your email address and a token instead of requiring OAuth2 flows. To enable API tokens: log in to Zendesk and go to Admin Center (click the gear icon in the left sidebar, then 'Admin Center', or go directly to yourcompany.zendesk.com/admin). In Admin Center, navigate to Apps and Integrations → APIs → Zendesk API. Toggle 'Token access' to Enabled if it is not already. Click 'Add API token'. Give the token a description like 'Lovable Integration' so you can identify it later. Click 'Create'. Zendesk displays the token once — copy it immediately, as it will not be shown again after you close the dialog. The token is used alongside your Zendesk agent email address for authentication. The HTTP Basic Auth format for the Zendesk API is: username = your_email/token (literally with '/token' appended) and password = your_api_token_value. For example, if your email is admin@yourcompany.com and your token is abc123xyz, the authorization header is: Authorization: Basic base64(admin@yourcompany.com/token:abc123xyz) You do not need to calculate this base64 encoding manually — the Edge Function does it using Deno's built-in btoa() function. Also note your Zendesk subdomain: it is the 'yourcompany' in yourcompany.zendesk.com. All Zendesk API base URLs are https://yoursubdomain.zendesk.com/api/v2 — the subdomain is required for every API call.
Pro tip: Zendesk shows the API token only once at creation time. If you close the dialog without copying the token, you will need to delete it and create a new one. Keep a copy in your password manager as a backup before storing it in Cloud → Secrets.
Expected result: Token access is enabled in Zendesk Admin Center → APIs → Zendesk API, and a new API token is created and copied. The token is a long alphanumeric string.
Store Zendesk credentials in Cloud Secrets
Store Zendesk credentials in Cloud Secrets
Add all Zendesk credentials to Lovable's Cloud Secrets panel. Three values are required: your Zendesk agent email address, the API token you just created, and your Zendesk subdomain. In Lovable, click the '+' icon at the top of the editor to open the Cloud panel. Navigate to the Secrets tab. Add three secrets: - ZENDESK_EMAIL — your Zendesk agent email address (e.g., admin@yourcompany.com) - ZENDESK_API_TOKEN — the API token string you copied in the previous step - ZENDESK_SUBDOMAIN — just the subdomain part (e.g., 'yourcompany', not 'yourcompany.zendesk.com') The Edge Function will construct the Base64-encoded authorization header from these three values. The authentication string format is: btoa(email + '/token:' + apiToken) — note the '/token' suffix on the email, which tells Zendesk you are using API token auth rather than password auth. Store the subdomain without the protocol or .zendesk.com suffix — just the company name part. The Edge Function constructs the full API URL as https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/. If you operate multiple Zendesk instances (e.g., one for US customers, one for EU), you can add ZENDESK_SUBDOMAIN_EU as a separate secret and route requests to the appropriate instance based on the user's region.
Pro tip: The ZENDESK_SUBDOMAIN value should be only the company portion — for example, if your Zendesk URL is acme.zendesk.com, the value should be 'acme' with no prefix or suffix. Including the full domain causes double-domain URLs like acme.zendesk.com.zendesk.com in API calls.
Expected result: Three secrets appear in Cloud → Secrets: ZENDESK_EMAIL, ZENDESK_API_TOKEN, and ZENDESK_SUBDOMAIN, all with masked values.
Create the Zendesk Support API proxy Edge Function
Create the Zendesk Support API proxy Edge Function
Build the Edge Function that handles all Zendesk operations. The function constructs the Base64 auth header from the stored credentials and proxies requests to the appropriate Zendesk API endpoint. Zendesk's API v2 follows consistent REST conventions. Key endpoints: GET /tickets.json for all tickets, GET /tickets/{id}.json for a single ticket, POST /tickets.json to create a ticket, PUT /tickets/{id}.json to update a ticket, GET /search.json?query=requester:email@example.com to search tickets by requester. Ticket creation requires a 'ticket' wrapper object in the request body with nested 'comment' for the first message. The comment must include a 'body' field. Ticket fields include subject, description (via the first comment body), priority (urgent/high/normal/low), status (new/open/pending/solved/closed), and tags array. For user-facing support portals where users should only see their own tickets, use the search endpoint with a requester filter rather than fetching all tickets and filtering client-side — this is both more efficient and more secure. A useful pattern for linking Zendesk users to your Supabase users: store the Zendesk user ID in your Supabase users table after the first ticket creation. On subsequent interactions, pass the Zendesk user ID directly instead of searching by email.
Create an Edge Function called zendesk-support at supabase/functions/zendesk-support/index.ts. Accept POST with { action, params }. Support actions: create_ticket, get_ticket, list_user_tickets, update_ticket_status, add_comment. Build the Base64 auth header from ZENDESK_EMAIL, ZENDESK_API_TOKEN, and ZENDESK_SUBDOMAIN secrets. Return the Zendesk API response. Include CORS headers.
Paste this in Lovable chat
1import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'23const corsHeaders = {4 'Access-Control-Allow-Origin': '*',5 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',6}78function getZendeskHeaders() {9 const email = Deno.env.get('ZENDESK_EMAIL')!10 const token = Deno.env.get('ZENDESK_API_TOKEN')!11 if (!email || !token) throw new Error('Zendesk credentials not configured in secrets')12 const encoded = btoa(`${email}/token:${token}`)13 return {14 Authorization: `Basic ${encoded}`,15 'Content-Type': 'application/json',16 }17}1819function zdUrl(path: string) {20 const subdomain = Deno.env.get('ZENDESK_SUBDOMAIN')!21 return `https://${subdomain}.zendesk.com/api/v2${path}`22}2324serve(async (req) => {25 if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders })2627 try {28 const { action, params = {} } = await req.json()29 const headers = getZendeskHeaders()30 let resp: Response3132 switch (action) {33 case 'create_ticket':34 resp = await fetch(zdUrl('/tickets.json'), {35 method: 'POST',36 headers,37 body: JSON.stringify({38 ticket: {39 subject: params.subject,40 comment: { body: params.description },41 requester: { name: params.requesterName, email: params.requesterEmail },42 priority: params.priority || 'normal',43 tags: params.tags || [],44 },45 }),46 })47 break4849 case 'get_ticket':50 resp = await fetch(zdUrl(`/tickets/${params.ticketId}.json`), { headers })51 break5253 case 'list_user_tickets':54 resp = await fetch(zdUrl(`/search.json?query=type:ticket requester:${encodeURIComponent(params.email)}&sort_by=created_at&sort_order=desc`), { headers })55 break5657 case 'update_ticket_status':58 resp = await fetch(zdUrl(`/tickets/${params.ticketId}.json`), {59 method: 'PUT',60 headers,61 body: JSON.stringify({ ticket: { status: params.status } }),62 })63 break6465 case 'add_comment':66 resp = await fetch(zdUrl(`/tickets/${params.ticketId}.json`), {67 method: 'PUT',68 headers,69 body: JSON.stringify({70 ticket: {71 comment: {72 body: params.body,73 public: params.public !== false,74 },75 },76 }),77 })78 break7980 default:81 return new Response(JSON.stringify({ error: 'Unknown action' }), {82 status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' },83 })84 }8586 const data = await resp.json()87 return new Response(JSON.stringify(data), {88 status: resp.ok ? 200 : resp.status,89 headers: { ...corsHeaders, 'Content-Type': 'application/json' },90 })91 } catch (error) {92 return new Response(JSON.stringify({ error: error.message }), {93 status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },94 })95 }96})Pro tip: For the list_user_tickets action, the Zendesk Search API returns results in a 'results' array, not a 'tickets' array. Destructure accordingly: const tickets = data.results. The search endpoint is more flexible than the per-user tickets endpoint and works for finding tickets by any field.
Expected result: The zendesk-support Edge Function deploys in Cloud → Edge Functions. Test with action 'list_user_tickets' and your own email — you should see existing tickets from your Zendesk account in the response.
Build the customer support portal UI in Lovable
Build the customer support portal UI in Lovable
With the Edge Function deployed, use Lovable's chat to build the support portal UI. A complete support portal has two main views: a ticket list showing the user's support history with status badges, and a ticket detail view showing the full conversation thread with a reply form. For the ticket list, call list_user_tickets with the authenticated user's email on component mount. Zendesk ticket statuses are: new (just created), open (being worked on), pending (waiting on customer), solved (agent marked solved), and closed (permanently closed). Color-code them: open/new in yellow, pending in blue, solved/closed in gray. For the ticket submission form, collect subject and description at minimum. Add a priority selector (Normal, High, Urgent) if appropriate for your use case. On submission, call create_ticket and store the returned ticket.id in your Supabase database linked to the user's account for future reference. For the ticket detail view, Zendesk returns ticket comments via a separate endpoint: GET /tickets/{id}/comments.json. Add a 'get_comments' action to your Edge Function to support this. Render comments as a chat bubble thread with timestamps, differentiating between public replies (visible to customer) and private agent notes (internal). For production support portals serving large volumes of customers, RapidDev's team can help implement Zendesk webhook integration for real-time ticket status updates that push to your app rather than requiring polling.
Build a support portal page with two views: a ticket list and a new ticket form. On load, call the zendesk-support Edge Function with action 'list_user_tickets' and the current user's email. Show tickets in a list with subject, status badge, and created date. Add a New Ticket button that opens a form with subject and description fields. On form submit, call create_ticket and show the new ticket in the list. Add status badge colors: yellow for open/new, blue for pending, gray for solved/closed.
Paste this in Lovable chat
Pro tip: Zendesk ticket IDs are numeric and sequential within your subdomain. Store the Zendesk ticket ID in your Supabase database alongside the user ID when creating tickets — this enables reliable ticket lookups without having to search by email every time.
Expected result: The support portal page renders the user's Zendesk ticket history. The New Ticket form creates a real ticket in Zendesk visible in your Zendesk agent workspace. Status badges reflect the correct ticket state.
Common use cases
In-app support portal showing user ticket history
Build a support section in your Lovable app where users can view all their open, pending, and resolved tickets without leaving your product. The Edge Function fetches tickets from Zendesk filtered by the user's email, returning ticket subject, status, last update, and ticket ID. Users can click a ticket to view its comment thread and add new replies — all within your branded app UI.
Create an Edge Function called zendesk-tickets that accepts a GET with a userEmail query parameter and fetches all tickets for that user from the Zendesk API using ZENDESK_EMAIL, ZENDESK_API_TOKEN, and ZENDESK_SUBDOMAIN from secrets. Return ticket id, subject, status, created_at, and updated_at. Then build a support page in Lovable showing the user's ticket list with status badges and a New Ticket button.
Copy this prompt to try it in Lovable
Automatic ticket creation from app error events
When users encounter errors or blocked states in your Lovable app (failed payments, failed exports, permission errors), automatically create a Zendesk ticket on their behalf with relevant context. The Edge Function creates a ticket with the error details, user ID, and app state, linking it to the user's Zendesk account. Your support team sees the full context without the user having to describe what went wrong.
Create an Edge Function called zendesk-create-ticket that accepts { requesterEmail, requesterName, subject, description, priority, tags } and creates a Zendesk ticket using ZENDESK_EMAIL, ZENDESK_API_TOKEN, and ZENDESK_SUBDOMAIN from secrets. Set priority to 'normal' by default. Return the ticket ID and URL. Call this from my app's error boundary component whenever a critical error is caught, passing the error message and component stack as the description.
Copy this prompt to try it in Lovable
Agent productivity dashboard showing ticket queue
Build an internal dashboard for your support team that shows their Zendesk ticket queue, filterable by status and priority, with ticket details and quick-action buttons for common responses. Agents can update ticket status and add internal notes from the Lovable dashboard without switching to the Zendesk UI for routine triage operations.
Create Edge Functions to fetch Zendesk tickets with status and priority filters, get a specific ticket's comments, and update a ticket's status. Build an agent dashboard with a filterable ticket table showing subject, requester name, status, priority, and last updated date. Add a ticket detail panel that shows the full comment thread and an Update Status dropdown. Include an Add Internal Note text area that posts a private comment to the ticket.
Copy this prompt to try it in Lovable
Troubleshooting
Zendesk API returns 401 Unauthorized with 'Couldn't authenticate you' error
Cause: The Authorization header is incorrectly formatted. Zendesk API token auth requires the email with '/token' appended before the colon, not just the email.
Solution: Ensure the base64 encoding uses the format 'email/token:api_token_value' (not 'email:api_token_value'). In your Edge Function, verify the encoded string is: btoa(ZENDESK_EMAIL + '/token:' + ZENDESK_API_TOKEN). Also confirm that Token access is enabled in Zendesk Admin Center → Apps and Integrations → APIs → Zendesk API.
1// Correct auth encoding for Zendesk token auth:2const encoded = btoa(`${email}/token:${token}`) // note: /token before the colonZendesk API returns 404 with 'Resource Not Found' for all endpoints
Cause: The ZENDESK_SUBDOMAIN contains the full domain (yourcompany.zendesk.com) instead of just the subdomain (yourcompany), resulting in a URL like yourcompany.zendesk.com.zendesk.com/api/v2/.
Solution: Update the ZENDESK_SUBDOMAIN secret to contain only the company name portion of your Zendesk URL, without 'https://' or '.zendesk.com'. For example, if your Zendesk URL is acme.zendesk.com, the secret value should be 'acme'.
Ticket creation succeeds but the requester appears as the API user instead of the customer
Cause: When creating tickets without specifying a requester, Zendesk defaults to the authenticated user (your API email). The requester field must be explicitly set.
Solution: Always include the requester object in the ticket creation payload with the customer's name and email. This creates or finds the Zendesk user record for the customer and links them to the ticket as the requester.
1// Always include requester in ticket creation:2body: JSON.stringify({3 ticket: {4 subject: params.subject,5 comment: { body: params.description },6 requester: { name: params.requesterName, email: params.requesterEmail }, // required7 },8})Search for tickets by user email returns empty results for valid users
Cause: The Zendesk Search API syntax for requester filtering uses 'requester:email@example.com' format. If the email contains special characters or is not properly encoded, the search returns empty results.
Solution: Use encodeURIComponent() when embedding the email in the search query URL to handle special characters in email addresses. Also verify the email exists in Zendesk as an end-user (it is created automatically on first ticket submission). New accounts with no tickets yet will always return empty search results.
1// Properly encode email in search query:2const query = `type:ticket requester:${encodeURIComponent(params.email)}`3const url = zdUrl(`/search.json?query=${encodeURIComponent(query)}`)Best practices
- Create a dedicated Zendesk agent account for the API integration rather than using your personal admin account — this isolates the integration token from personnel changes.
- Store the Zendesk ticket ID in your Supabase database when creating tickets, so future lookups use direct ticket IDs rather than expensive search queries.
- Use the Zendesk Search API for user-specific ticket lists — it is more efficient than fetching all tickets and filtering, and avoids returning tickets that belong to other customers.
- Implement non-blocking ticket creation in user-facing error handlers — ticket creation failures should not block the user from seeing an error message or continuing their session.
- Add Zendesk ticket tags consistently (e.g., 'lovable-app', 'in-app-portal') to distinguish programmatically created tickets from email-submitted ones in your Zendesk agent workspace.
- Validate requester email format before calling the Zendesk API — Zendesk rejects malformed emails with a 422 error that is confusing if not caught at the form level.
- For customer-facing portals, filter out 'solved' and 'closed' tickets from the default view but provide a 'Show resolved tickets' toggle — most users only care about open issues.
Alternatives
Choose Freshdesk if cost is a primary concern — Freshdesk's free plan supports up to 10 agents and includes API access, making it significantly more affordable than Zendesk for small teams.
Choose Intercom if you want conversational in-app messaging alongside support tickets — Intercom's messenger widget is more native to modern SaaS products than Zendesk's traditional ticket queue.
Choose LiveChat if real-time chat is the primary support channel and you want a simpler, dedicated chat platform without Zendesk's full ticketing complexity.
Frequently asked questions
Does Zendesk have a free plan that supports API access?
Zendesk does not have a permanent free plan for support ticketing. There is a 14-day free trial, and Zendesk offers a free plan for very small businesses in select regions. All paid plans (Suite Team at $55/agent/month and above) include API access. For testing your Lovable integration, the free trial is sufficient to build and validate the integration before committing to a plan.
Can users submit support tickets through Lovable without a Zendesk account?
Yes — when you create a Zendesk ticket via the API with a requester email, Zendesk automatically creates an end-user account for that email address if one does not already exist. End-user accounts are not agent accounts and do not cost additional seats. Your customers never need to log in to Zendesk directly — they interact entirely with your Lovable support portal UI.
How do I receive Zendesk updates in real time in my Lovable app?
Use Zendesk webhooks (formerly called Targets and Triggers) to push ticket updates to your Lovable Edge Function. In Zendesk Admin Center → Objects and Rules → Webhooks, create a webhook pointing to your Edge Function URL. Create triggers in Admin Center → Business Rules → Triggers that fire the webhook when tickets are updated (e.g., status changes to 'solved'). Your Edge Function can then update a Supabase table, triggering Realtime notifications to your app.
What is the difference between public comments and internal notes in Zendesk?
Public comments are visible to both the customer and your support agents — they form the conversation thread the customer sees when they check their ticket status. Internal notes are private comments visible only to agents and never shown to customers. When adding comments via the API, set 'public: true' for customer-visible replies and 'public: false' for internal agent notes. Most customer support portals in Lovable should only display public comments.
Can I integrate Lovable with Zendesk Chat (formerly Zopim) as well as the support ticketing?
Zendesk Chat uses a separate API and widget system. For Lovable apps, embedding the Zendesk Chat widget requires adding the Zendesk Web Widget JavaScript snippet to your HTML, which conflicts with Lovable's React-based architecture. The support ticket API (this guide) is significantly easier to integrate and covers most customer support use cases. For real-time chat, consider Intercom or LiveChat, which offer React-compatible widget approaches.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation