To use Zendesk with V0, generate your support UI in V0, then create a Next.js API route at app/api/zendesk/route.ts that uses the Zendesk REST API with your API token stored in Vercel environment variables. You can create tickets, fetch ticket data, and embed the Zendesk Web Widget for live chat — all from your V0-generated Next.js app.
Building Customer Support Features in Your V0 App with Zendesk
Zendesk is the most widely deployed customer support platform, used by companies from startups to enterprise. For V0 builders, integrating Zendesk means users can submit support tickets directly from your app without leaving it, your support team can manage those tickets in their familiar Zendesk workspace, and you can display ticket status to users in a dedicated support history section. The integration pattern is standard V0 architecture: V0 builds the UI, and a Next.js API route proxies requests to Zendesk's REST API.
Zendesk offers two fundamentally different integration approaches that serve different use cases. The REST API approach (covered in depth here) lets you programmatically create, read, update, and close tickets — giving you full control to build custom support UIs, dashboards, and automations. The Web Widget approach is a simpler alternative: Zendesk provides a JavaScript snippet you paste into your page layout, and it renders a pre-built chat or contact form button. The widget requires no API credentials and takes about two minutes to embed. Most V0 projects benefit from combining both: use the widget for the quick live chat button, and use API routes for custom ticket forms and user-facing ticket history.
Authentication with Zendesk's REST API uses HTTP Basic Auth with the format email/token:api_token. This means your agent email address and an API token (generated from Zendesk Admin Center) are base64-encoded in the Authorization header. This credential must live exclusively in your server-side API route — exposing it in client-side code would allow anyone to create unlimited tickets under your agent account or access your customer data.
Integration method
V0 generates your support interface components while a Next.js API route handles all Zendesk REST API calls server-side using Basic Auth with your API token, keeping credentials out of the browser. For the Zendesk Web Widget (live chat), V0 adds the JavaScript snippet to your layout for client-side embedding, which does not require an API token in the browser.
Prerequisites
- A V0 account at v0.dev with a Next.js project
- A Zendesk account with admin access at yoursubdomain.zendesk.com
- A Zendesk API token generated from Zendesk Admin Center → Apps and Integrations → APIs → Zendesk API
- Your Zendesk subdomain (the part before .zendesk.com in your URL)
- A Vercel account with your V0 project deployed via GitHub
Step-by-step guide
Generate the Support UI in V0
Generate the Support UI in V0
Start by prompting V0 to build your support interface. The most common starting point is a contact form that users fill out to submit a support ticket. V0 can generate polished form interfaces with field validation, loading states, and success/error feedback using React and Tailwind CSS. The key to a good V0 prompt for support UIs is specifying the exact fields you need and the API endpoint the form should call. Zendesk ticket creation requires at minimum a subject and a description body. You can optionally include priority (low, normal, high, urgent), tags for routing, and custom fields if your Zendesk account has them configured. Ask V0 to include user identification in the ticket creation request. For logged-in users, include their email address in the POST body — this allows Zendesk to associate the ticket with the user's profile and lets agents see the user's full history. For anonymous users, include an email field in the form itself. A common issue with V0-generated forms is the lack of client-side validation. A user submitting a blank subject or a one-word description will create a low-quality ticket that frustrates your support team. Ask V0 explicitly to validate that the subject has at least 10 characters and the description at least 30, and to display inline error messages under each field before the form is submitted. V0 often generates these validation patterns cleanly using React state or react-hook-form.
Create a 'Contact Support' page with a centered card. Include: a Subject input (required, minimum 10 chars) with label and red error message if too short, a Description textarea (required, minimum 30 chars, 5 rows) with character count, a Category select dropdown with options 'Bug Report', 'Feature Request', 'Billing Question', 'Other', and a blue Submit button. On submit, POST to /api/zendesk/tickets with { subject, description, category }. Show a green success card with 'Ticket #[id] created' after success. Show a red error banner if the request fails.
Paste this in V0 chat
Pro tip: Ask V0 to disable the submit button and show a spinner while the request is in progress to prevent duplicate ticket submissions.
Expected result: V0 generates a validated support form component with proper error states, loading indicator, and success confirmation that calls /api/zendesk/tickets on submit.
Create the Zendesk API Route
Create the Zendesk API Route
Create the server-side Next.js API route that will handle ticket creation and retrieval. In your project, create app/api/zendesk/tickets/route.ts. This route uses Zendesk's REST API v2, which is the current supported version. Zendesk's REST API uses HTTP Basic Auth where the username is your agent email address followed by /token (for example agent@company.com/token) and the password is the API token from your Zendesk account. These credentials must be base64-encoded. In Node.js, Buffer.from('email/token:apikey').toString('base64') handles the encoding. Store the raw values in environment variables and do the encoding in your API route code. For creating tickets (POST requests), the Zendesk API endpoint is https://{subdomain}.zendesk.com/api/v2/tickets.json. The request body must include a ticket object with subject and comment.body fields at minimum. You can also set the requester's name and email, priority, tags, and custom fields. The API returns the created ticket object with its numeric ID, which you can return to the V0 component to display in the success message. For fetching tickets (GET requests), use the search endpoint https://{subdomain}.zendesk.com/api/v2/search.json?query=type:ticket requester:{email} to find tickets by requester email. This returns a results array with ticket objects including status, priority, subject, and created_at timestamp. Be aware that Zendesk's API has rate limits: the core API allows 700 requests per minute for most plans. For high-volume applications, consider caching GET responses for ticket lists — a 30-second cache for a user's ticket history is usually acceptable and dramatically reduces API calls.
Add a Next.js API route at app/api/zendesk/tickets/route.ts. For POST requests, accept { subject, description, category, requesterEmail } and create a Zendesk ticket using the Zendesk REST API v2 with Basic Auth using ZENDESK_EMAIL, ZENDESK_API_TOKEN, and ZENDESK_SUBDOMAIN from environment variables. Return the created ticket ID. For GET requests with an email query parameter, search for tickets by that requester email and return an array of tickets with id, subject, status, priority, and created_at.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23const ZENDESK_SUBDOMAIN = process.env.ZENDESK_SUBDOMAIN!;4const ZENDESK_EMAIL = process.env.ZENDESK_EMAIL!;5const ZENDESK_API_TOKEN = process.env.ZENDESK_API_TOKEN!;67function getZendeskAuth(): string {8 return Buffer.from(`${ZENDESK_EMAIL}/token:${ZENDESK_API_TOKEN}`).toString('base64');9}1011const ZENDESK_BASE = `https://${ZENDESK_SUBDOMAIN}.zendesk.com/api/v2`;1213export async function POST(request: NextRequest) {14 try {15 const { subject, description, category, requesterEmail } = await request.json();1617 if (!subject || !description) {18 return NextResponse.json(19 { error: 'Subject and description are required' },20 { status: 400 }21 );22 }2324 const ticketBody = {25 ticket: {26 subject,27 comment: { body: description },28 tags: [category?.toLowerCase().replace(/\s+/g, '_') || 'general'],29 ...(requesterEmail && {30 requester: { email: requesterEmail },31 }),32 },33 };3435 const response = await fetch(`${ZENDESK_BASE}/tickets.json`, {36 method: 'POST',37 headers: {38 Authorization: `Basic ${getZendeskAuth()}`,39 'Content-Type': 'application/json',40 },41 body: JSON.stringify(ticketBody),42 });4344 if (!response.ok) {45 const error = await response.json();46 return NextResponse.json(47 { error: error.description || 'Failed to create ticket' },48 { status: response.status }49 );50 }5152 const data = await response.json();53 return NextResponse.json({54 ticketId: data.ticket.id,55 status: data.ticket.status,56 });57 } catch (error) {58 console.error('Zendesk ticket creation error:', error);59 return NextResponse.json(60 { error: 'Failed to create support ticket' },61 { status: 500 }62 );63 }64}6566export async function GET(request: NextRequest) {67 try {68 const { searchParams } = new URL(request.url);69 const email = searchParams.get('email');7071 if (!email) {72 return NextResponse.json(73 { error: 'email query parameter is required' },74 { status: 400 }75 );76 }7778 const query = encodeURIComponent(`type:ticket requester:${email}`);79 const response = await fetch(80 `${ZENDESK_BASE}/search.json?query=${query}&sort_by=created_at&sort_order=desc`,81 {82 headers: {83 Authorization: `Basic ${getZendeskAuth()}`,84 'Content-Type': 'application/json',85 },86 }87 );8889 if (!response.ok) {90 return NextResponse.json(91 { error: 'Failed to fetch tickets' },92 { status: response.status }93 );94 }9596 const data = await response.json();97 const tickets = data.results.map((t: Record<string, unknown>) => ({98 id: t.id,99 subject: t.subject,100 status: t.status,101 priority: t.priority,102 created_at: t.created_at,103 }));104105 return NextResponse.json({ tickets });106 } catch (error) {107 console.error('Zendesk fetch tickets error:', error);108 return NextResponse.json(109 { error: 'Failed to fetch tickets' },110 { status: 500 }111 );112 }113}Pro tip: Use the /token suffix in the Zendesk auth username (agent@company.com/token) when authenticating with an API token. Without /token, Zendesk expects a password instead of an API token.
Expected result: The API route handles both POST (create ticket) and GET (fetch tickets by email) requests. POST requests return the new ticket ID, GET requests return a filtered ticket array.
Add Zendesk Credentials to Vercel Environment Variables
Add Zendesk Credentials to Vercel Environment Variables
Your API route reads ZENDESK_SUBDOMAIN, ZENDESK_EMAIL, and ZENDESK_API_TOKEN from environment variables. Configure these in Vercel so your serverless functions can access them. Go to Vercel Dashboard → your project → Settings → Environment Variables. Add the following: ZENDESK_SUBDOMAIN: The subdomain portion of your Zendesk URL. If you access Zendesk at yourcompany.zendesk.com, the subdomain is yourcompany. Do not include .zendesk.com. ZENDESK_EMAIL: The email address of your Zendesk agent account that the API token belongs to. This should be a dedicated service account or admin account, not a personal email if you can help it. ZENDESK_API_TOKEN: The API token generated from Zendesk Admin Center → Apps and Integrations → APIs → Zendesk API → Add API Token. Copy the token immediately after generation — Zendesk only shows it once. Do NOT add NEXT_PUBLIC_ prefix — this is a server-only secret. If you are also embedding the Zendesk Web Widget, add NEXT_PUBLIC_ZENDESK_WIDGET_KEY with your widget key (found in Zendesk Admin Center → Channels → Web Widget → Installation). This one does use the NEXT_PUBLIC_ prefix because the widget script is loaded client-side and the key is not sensitive — it is the same key included in every page's HTML. After adding all variables, redeploy your Vercel project. The new environment variables will be available to all serverless function invocations after the deployment completes.
1# .env.local — local development credentials2ZENDESK_SUBDOMAIN=yourcompany3ZENDESK_EMAIL=agent@yourcompany.com4ZENDESK_API_TOKEN=your_api_token_here5NEXT_PUBLIC_ZENDESK_WIDGET_KEY=your_widget_key_herePro tip: Create a dedicated Zendesk API token named after your application (e.g., 'V0 App Production'). If you ever need to revoke access, you can delete just this token without affecting other integrations.
Expected result: All four environment variables appear in Vercel Dashboard's Environment Variables section. After redeployment, the API route creates tickets successfully without authentication errors.
Embed the Zendesk Web Widget (Optional)
Embed the Zendesk Web Widget (Optional)
For the live chat experience, embed Zendesk's Web Widget by adding a script tag to your Next.js layout. The widget renders a help button in the bottom-right corner of every page and does not require any server-side API calls — users interact with it directly through Zendesk's hosted interface. In Next.js App Router, the correct approach is to create a client component (since it uses useEffect to load the script) and import it into your root layout.tsx. The widget script should be loaded after the page renders, not blocking the initial render. The Widget Key (also called the Snippet Key) is a public identifier — it is safe to expose in the browser and should use the NEXT_PUBLIC_ prefix so Next.js includes it in the client bundle. Find your widget key in Zendesk Admin Center → Channels → Messaging → Web Widget (or Classic Widget) → Installation tab. Note a V0-specific limitation here: if you ask V0 to add a script tag in the Head, V0 may generate code using next/head, which is the Pages Router pattern. For App Router, script injection should use next/script with strategy='lazyOnload' or a useEffect in a client component. If V0 generates the Pages Router pattern, ask it to refactor to the App Router approach using a 'use client' component with useEffect.
Create a ZendeskWidget client component in components/zendesk-widget.tsx that adds the Zendesk Web Widget to the page. Use 'use client' and useEffect to append a script tag to the document body with src 'https://static.zdassets.com/ekr/snippet.js?key=' plus the NEXT_PUBLIC_ZENDESK_WIDGET_KEY environment variable. Set the script id to 'ze-snippet'. Then import and add this component to the root app/layout.tsx so the widget appears on all pages.
Paste this in V0 chat
1'use client';23import { useEffect } from 'react';45declare global {6 interface Window {7 zE?: (...args: unknown[]) => void;8 zESettings?: Record<string, unknown>;9 }10}1112export function ZendeskWidget() {13 useEffect(() => {14 if (!process.env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY) return;15 if (document.getElementById('ze-snippet')) return; // Prevent duplicate loading1617 const script = document.createElement('script');18 script.id = 'ze-snippet';19 script.src = `https://static.zdassets.com/ekr/snippet.js?key=${process.env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY}`;20 script.async = true;21 document.body.appendChild(script);2223 return () => {24 // Cleanup on unmount if needed25 };26 }, []);2728 return null;29}Pro tip: You can programmatically control the Zendesk Web Widget after it loads using the window.zE() command API — for example, window.zE('messenger', 'open') opens the chat window, and window.zE('messenger', 'prefill', { name: { value: 'User Name' } }) pre-fills the user's name.
Expected result: The Zendesk help button appears in the bottom-right corner of all pages. Clicking it opens the Web Widget interface where users can chat with support or submit a message.
Common use cases
In-App Support Ticket Submission Form
A SaaS founder wants users to submit support requests without leaving the app. V0 generates a clean contact form with subject, description, and priority fields. When submitted, a Next.js API route creates a Zendesk ticket pre-populated with the user's account information, so support agents receive complete context without asking follow-up questions.
Create a 'Contact Support' page with a form that has: a Subject text input, a Description textarea with at least 4 rows, a Priority dropdown with options Normal/High/Urgent, and a Submit button. On submit, POST to /api/zendesk/tickets with the form data. Show a success message with a ticket number after submission. Show a loading spinner on the button during submission. Display an error message if the request fails.
Copy this prompt to try it in V0
Customer Ticket History Dashboard
A subscription app shows users a list of their open and resolved support tickets. V0 generates the ticket list with status badges, timestamps, and a link to view each ticket. The API route fetches the user's tickets from Zendesk filtered by their email address.
Build a 'My Support Tickets' page that shows a list of support tickets with columns for Ticket ID (as a link), Subject, Status (as a colored badge: green for Open, gray for Solved, yellow for Pending), Priority, and Last Updated date. Fetch the data from GET /api/zendesk/tickets with an email query parameter. Show an empty state with 'No tickets yet' if the list is empty. Add a 'Create New Ticket' button that links to the support form.
Copy this prompt to try it in V0
Zendesk Web Widget Live Chat Embed
A marketing site or app wants a live chat button in the bottom-right corner. V0 adds the Zendesk Web Widget JavaScript snippet to the Next.js layout file. Users can start a live chat or leave a message without any API routes or credentials needed.
Add the Zendesk Web Widget to the app layout. Create a ZendeskWidget client component that uses useEffect to dynamically load the Zendesk Web Widget script with the key from the NEXT_PUBLIC_ZENDESK_WIDGET_KEY environment variable. Mount this component in the root layout so the chat button appears on all pages.
Copy this prompt to try it in V0
Troubleshooting
API route returns 401 'Could not authenticate you'
Cause: The Zendesk Basic Auth credentials are malformed. Either the /token suffix is missing from the email (required when using API tokens instead of passwords), the API token is incorrect, or the base64 encoding is wrong.
Solution: Verify the auth format is exactly email@domain.com/token:your_api_token before base64 encoding. Check that ZENDESK_EMAIL includes the /token suffix in the Buffer.from() call, not in the environment variable itself. Regenerate the API token in Zendesk Admin Center if the current one may be compromised.
1// Correct auth format — include /token after email address2const auth = Buffer.from(`${process.env.ZENDESK_EMAIL}/token:${process.env.ZENDESK_API_TOKEN}`).toString('base64');fetch returns 'getaddrinfo ENOTFOUND undefined.zendesk.com'
Cause: The ZENDESK_SUBDOMAIN environment variable is not set in Vercel, so the template literal produces undefined.zendesk.com as the hostname.
Solution: In Vercel Dashboard → Settings → Environment Variables, verify ZENDESK_SUBDOMAIN is set to just the subdomain (e.g., yourcompany), not the full URL. After adding or updating environment variables, redeploy the project.
Zendesk Web Widget does not appear on the deployed site
Cause: Either NEXT_PUBLIC_ZENDESK_WIDGET_KEY is not set as an environment variable, or the script is being loaded in a Server Component where useEffect cannot run.
Solution: Confirm NEXT_PUBLIC_ZENDESK_WIDGET_KEY is saved in Vercel environment variables and that the ZendeskWidget component has 'use client' at the top. Check the browser console for script loading errors. Verify the widget key in Zendesk Admin Center → Channels → Web Widget → Installation.
Ticket search returns empty results even though tickets exist
Cause: The Zendesk search API uses the requester email to filter tickets, but the email passed in the query does not match the requester email stored in Zendesk — for example, due to case sensitivity or extra spaces.
Solution: Trim and lowercase the email before including it in the search query. Also verify that the tickets were actually created with that email address as the requester — check the ticket in Zendesk's admin view to see the requester email.
1const email = searchParams.get('email')?.trim().toLowerCase();2const query = encodeURIComponent(`type:ticket requester:${email}`);Best practices
- Store ZENDESK_API_TOKEN without any NEXT_PUBLIC_ prefix — it is a server-side secret that must never be exposed to the browser.
- Use the /token format in your Zendesk email auth (email@domain.com/token) when authenticating with API tokens, not passwords.
- Create a dedicated Zendesk API token named after your application so you can revoke access specifically without disrupting other integrations.
- Pre-populate ticket requester fields with the logged-in user's email and name from your auth system to give support agents complete context.
- Cache ticket list responses for 30-60 seconds in your API route to reduce Zendesk API calls for users who refresh their ticket history frequently.
- Use Zendesk tags to auto-route tickets from different parts of your app — for example, billing_question or onboarding_issue — so agents know the context before reading the ticket.
- Validate required fields (subject minimum 10 chars, description minimum 30 chars) before submitting to Zendesk to improve ticket quality for your support team.
- For the Web Widget, use the window.zE() API to pre-fill user information when they are logged in, reducing friction in the support request process.
Alternatives
Freshdesk is a good alternative if you want a similar ticketing system with a more affordable entry-level plan and comparable REST API capabilities.
Intercom is a better choice if you want a product-led conversational support experience with in-app messaging and proactive outreach rather than traditional ticket management.
Zendesk Sunshine Conversations is the right choice if you specifically need a conversational messaging API with channel routing across WhatsApp, Messenger, and SMS rather than a standard helpdesk.
LiveChat is a better choice if your primary need is real-time live chat with visitors rather than structured ticket management and support workflows.
Frequently asked questions
Can I use Zendesk with V0 without writing any API routes?
Yes, for the live chat use case the Zendesk Web Widget requires no API routes. You add a JavaScript snippet to your Next.js layout and users interact with Zendesk's hosted interface directly. However, for programmatic ticket creation from a custom form, for displaying ticket status to users, or for any integration beyond the standard widget, you need a Next.js API route.
Why does V0 generate Pages Router patterns for Zendesk script injection?
V0 may generate code using next/head or similar Pages Router patterns when asked to add external scripts. For App Router (which V0's generated projects use by default), the correct approach is a client component with useEffect to append the script, or using Next.js's next/script component with strategy='lazyOnload'. If V0 generates the wrong pattern, ask it to refactor to a 'use client' component approach.
Does creating tickets via the API count against my Zendesk plan's ticket limit?
Yes, tickets created via the API are identical to tickets created through the widget or email — they count against your plan's agent seat and ticket volume limits. On pay-as-you-go plans, API-created tickets incur the same cost as any other ticket. Check your current Zendesk plan to understand the ticket volume limits before going live with a high-traffic integration.
How do I attach files to Zendesk tickets created from a V0 form?
Zendesk's file attachment process requires a two-step API call: first upload the file to Zendesk's upload endpoint (POST /api/v2/uploads.json?filename=name.pdf) to get an upload token, then include that token in the ticket creation request's comment.uploads array. V0 can generate the file upload component, but the API route needs to handle the two-step process manually with separate fetch calls.
Can the Zendesk Web Widget pre-fill user information from my V0 app's auth?
Yes, after the Zendesk Web Widget loads you can pre-fill user data using the window.zE() command API. In your ZendeskWidget component, listen for the widget's ready event and then call window.zE('messenger:set', 'conversationFields', [...]) or the equivalent for your widget type. This is particularly useful if you have user authentication in your V0 app — you can pass the user's name and email so they do not need to type them in the widget.
What is the Zendesk API rate limit and how do I handle it?
Zendesk's Core API allows 700 requests per minute per account on most plans. For high-traffic applications, cache GET responses (like ticket lists) and avoid polling Zendesk on every page load. If you hit the rate limit, the API returns a 429 response with a Retry-After header. Add exponential backoff in your API route to handle 429 responses gracefully rather than propagating the error to the user.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation