Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with Qualtrics

Integrate Qualtrics with Bolt.new by calling the Qualtrics REST API through a Next.js API route — the API token is kept server-side and all requests are proxied to prevent CORS errors. Fetch survey responses, build an analytics dashboard, or distribute survey links by embedding the Qualtrics survey JavaScript snippet. Qualtrics requires a paid subscription; for accessible survey APIs without enterprise pricing, consider Typeform or SurveyMonkey.

What you'll learn

  • How to get Qualtrics API credentials and configure authentication for REST API v3
  • How to fetch survey lists and response data through a Next.js API route
  • How to build a survey analytics dashboard with response counts and key metrics
  • How to embed Qualtrics surveys in a Bolt.new app using the JavaScript embed snippet
  • How to export and display response data including NPS scores and text responses
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate19 min read25 minutesOtherApril 2026RapidDev Engineering Team
TL;DR

Integrate Qualtrics with Bolt.new by calling the Qualtrics REST API through a Next.js API route — the API token is kept server-side and all requests are proxied to prevent CORS errors. Fetch survey responses, build an analytics dashboard, or distribute survey links by embedding the Qualtrics survey JavaScript snippet. Qualtrics requires a paid subscription; for accessible survey APIs without enterprise pricing, consider Typeform or SurveyMonkey.

Build a Qualtrics Survey Analytics Dashboard in Bolt.new

Qualtrics is the leading enterprise survey and experience management platform, widely used by Fortune 500 companies, universities, and research institutions. Its REST API v3 provides programmatic access to surveys, response data, contacts, distributions, and reports — enabling you to build custom analytics dashboards, automate survey distribution, and integrate research data into other business applications. The API uses a straightforward API token authentication model, making integration technically accessible once you have the credentials.

The primary integration challenge for Bolt.new is CORS: Qualtrics's API does not allow direct browser requests from arbitrary origins. All API calls must be proxied through a server-side API route. In Bolt.new with a Next.js project, this means creating API routes that accept requests from your React frontend, add the Qualtrics API token from server-side environment variables, and forward the request to Qualtrics's servers. The Qualtrics API responds to your server, which then returns the data to your frontend — Qualtrics never sees the browser request directly.

Qualtrics requires a paid subscription — there is no free tier. The platform is enterprise-priced, starting around $1,500/year for basic plans and scaling significantly for enterprise features. If you are evaluating Qualtrics for a new project and cost is a concern, Typeform ($25/month) and SurveyMonkey ($25/month) both offer developer-friendly REST APIs with free or low-cost tiers. This guide assumes you have an existing Qualtrics account through your organization, which is the most common scenario for developers building integrations.

Integration method

Bolt Chat + API Route

Qualtrics integrates with Bolt.new through Next.js API routes that proxy requests to the Qualtrics REST API v3, keeping your API token server-side and handling CORS. Survey distribution uses either the Qualtrics survey embed snippet (JavaScript injected into your page) or API-generated anonymous links. Response data is fetched via the Qualtrics Export Responses API and displayed in a custom analytics dashboard. All outbound API calls work in Bolt's WebContainer preview; incoming survey response webhooks require a deployed URL.

Prerequisites

  • A Qualtrics account with API access — check Account Settings → Qualtrics IDs for your API token
  • Your Qualtrics datacenter ID (shown in Account Settings → Qualtrics IDs, looks like 'iad1' or 'eu')
  • At least one survey created in your Qualtrics account with some responses for testing
  • A Bolt.new account with a Next.js project open
  • Basic familiarity with Next.js API routes and environment variables

Step-by-step guide

1

Get Your Qualtrics API Credentials

Qualtrics uses a simple API token for authentication — there is no OAuth flow or developer application to register. The API token is tied to your individual Qualtrics user account and has the same permissions as your account in the Qualtrics platform. To find your credentials, log into your Qualtrics account and click on your account name in the top-right corner, then select Account Settings. In the Qualtrics IDs section (sometimes labelled as User Settings or Account IDs depending on your Qualtrics version), you will find three pieces of information you need: the API Token (a long alphanumeric string), the Datacenter ID (a short string like 'iad1', 'fra1', 'syd1', etc.), and your User ID (starts with UR_). Note the datacenter carefully — all API requests are made to a subdomain of qualtrics.com based on your datacenter (e.g., https://iad1.qualtrics.com/API/v3/ for the iad1 datacenter). If you do not see an API Token, click the Generate Token button. If the API section is not visible at all, your Qualtrics account may not have API access enabled. Contact your organization's Qualtrics administrator — API access is typically a feature that needs to be enabled at the organization level for security reasons. Once you have your token, keep it secret. Unlike the Supabase anon key, the Qualtrics API token grants full access to your Qualtrics account including creating, modifying, and deleting surveys and response data. It must never be exposed in client-side code. Add your credentials to your Bolt.new project's .env file. Use the QUALTRICS_ prefix for clarity. The datacenter ID is technically not sensitive (it is visible in your Qualtrics URL), but keeping it in .env alongside the token is good practice for consistency across environments.

.env
1# .env add to project root in Bolt
2# Found at: Qualtrics Account Account Settings Qualtrics IDs
3QUALTRICS_API_TOKEN=your_api_token_here
4QUALTRICS_DATACENTER=iad1
5# Your survey's ID (found in the survey URL: /survey-builder/SV_xxxxxxxx/...)
6QUALTRICS_SURVEY_ID=SV_xxxxxxxxxxxxxxx
7# For distribution features (found in Contacts Mailing Lists)
8QUALTRICS_MAILING_LIST_ID=ML_xxxxxxxxxxxxxxx

Pro tip: If you are building for multiple Qualtrics organizations (e.g., a consulting firm building a dashboard for multiple clients), you will need separate API tokens per client account. Consider storing tokens in a database table keyed by organization ID rather than in static environment variables.

Expected result: Your Qualtrics API token and datacenter ID are saved in the .env file. You can verify the token by making a test request to list your surveys in the next step.

2

Create a Qualtrics API Proxy Route

The Qualtrics REST API requires the API token to be sent in the X-API-TOKEN header. Because browser-based fetch requests cannot send custom headers to cross-origin servers without CORS approval, and Qualtrics's API servers do not whitelist arbitrary frontend origins, all Qualtrics API calls must go through a server-side API route in your Next.js app. Create a generic proxy utility and specific routes for the operations you need. The proxy pattern is consistent across all Qualtrics endpoints: your React frontend calls your Next.js API route (same-origin, no CORS issues), the API route adds the Qualtrics API token from environment variables, forwards the request to Qualtrics's servers, and returns the response. This is straightforward in Bolt's WebContainer during development — outbound HTTP calls to Qualtrics's servers work fine. For the dashboard use case, you need two endpoints: GET /api/qualtrics/surveys to list all surveys in the account, and GET /api/qualtrics/surveys/[surveyId]/summary to get response metadata for a specific survey. The Qualtrics API base URL is constructed from your datacenter: https://{datacenter}.qualtrics.com/API/v3/. The surveys endpoint returns an array of survey objects each containing the id, name, ownerId, lastModified, and isActive fields. A critical thing to understand about fetching responses: Qualtrics uses an asynchronous export pattern for large datasets. You cannot simply GET all responses — you must POST to start an export, poll until it completes, then download the exported file. For dashboard purposes where you only need aggregate counts (not individual responses), the survey summary endpoint provides response counts directly without the async export process. Use the export API only when you need to analyze or display individual responses.

Bolt.new Prompt

Create a Qualtrics API integration in Next.js. Build a utility at lib/qualtrics-client.ts with a qualtricsRequest function that takes an endpoint path and optional method/body, constructs the full Qualtrics API URL using process.env.QUALTRICS_DATACENTER, adds the X-API-TOKEN header using process.env.QUALTRICS_API_TOKEN, and returns the response JSON. Create API routes: app/api/qualtrics/surveys/route.ts that calls GET /surveys and returns the survey list with id, name, and lastModified. Also create app/api/qualtrics/surveys/[surveyId]/route.ts that calls GET /surveys/{surveyId} and returns the survey metadata including number of questions.

Paste this in Bolt.new chat

lib/qualtrics-client.ts
1// lib/qualtrics-client.ts
2export class QualtricsClient {
3 private baseUrl: string;
4 private apiToken: string;
5
6 constructor() {
7 const datacenter = process.env.QUALTRICS_DATACENTER;
8 const token = process.env.QUALTRICS_API_TOKEN;
9
10 if (!datacenter || !token) {
11 throw new Error('QUALTRICS_DATACENTER and QUALTRICS_API_TOKEN must be set');
12 }
13
14 this.baseUrl = `https://${datacenter}.qualtrics.com/API/v3`;
15 this.apiToken = token;
16 }
17
18 async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
19 const url = `${this.baseUrl}${endpoint}`;
20 const res = await fetch(url, {
21 ...options,
22 headers: {
23 'X-API-TOKEN': this.apiToken,
24 'Content-Type': 'application/json',
25 ...options.headers,
26 },
27 });
28
29 if (!res.ok) {
30 const error = await res.json().catch(() => ({ message: res.statusText }));
31 throw new Error(`Qualtrics API error ${res.status}: ${JSON.stringify(error)}`);
32 }
33
34 return res.json();
35 }
36
37 async getSurveys() {
38 const data = await this.request<{ result: { elements: QualtricssurveyItem[] } }>('/surveys');
39 return data.result.elements;
40 }
41
42 async getSurvey(surveyId: string) {
43 const data = await this.request<{ result: QualtricssurveyDetail }>(`/surveys/${surveyId}`);
44 return data.result;
45 }
46
47 async getResponseCounts(surveyId: string) {
48 const data = await this.request<{ result: { auditable: number; generated: number; deleted: number } }>(
49 `/responseexports/${surveyId}/responsecounts`
50 );
51 return data.result;
52 }
53}
54
55interface QualtricssurveyItem {
56 id: string;
57 name: string;
58 ownerId: string;
59 lastModified: string;
60 creationDate: string;
61 isActive: boolean;
62}
63
64interface QualtricssurveyDetail extends QualtricssurveyItem {
65 questions: Record<string, unknown>;
66}
67
68// app/api/qualtrics/surveys/route.ts
69import { NextResponse } from 'next/server';
70import { QualtricsClient } from '@/lib/qualtrics-client';
71
72export async function GET() {
73 try {
74 const client = new QualtricsClient();
75 const surveys = await client.getSurveys();
76
77 return NextResponse.json({
78 surveys: surveys.map(s => ({
79 id: s.id,
80 name: s.name,
81 lastModified: s.lastModified,
82 isActive: s.isActive,
83 })),
84 });
85 } catch (error) {
86 const message = error instanceof Error ? error.message : 'Failed to fetch surveys';
87 return NextResponse.json({ error: message }, { status: 500 });
88 }
89}

Pro tip: Qualtrics API responses are always wrapped in a 'result' property at the top level. For list endpoints, the actual data is in result.elements. For single-item endpoints, it is directly in result. Always destructure from result, not the top-level response.

Expected result: The /api/qualtrics/surveys endpoint returns your Qualtrics survey list in the Bolt preview. The survey names and modification dates are visible in the response, confirming the API token and datacenter are correctly configured.

3

Build the Survey Analytics Dashboard

With the API proxy routes working, build the analytics dashboard that displays survey data and response metrics. The dashboard provides research teams with a custom view of their Qualtrics data beyond the built-in Qualtrics reporting interface — useful when you need to combine Qualtrics data with other business metrics, apply custom filtering, or embed the data in an internal tool. The core dashboard features are: a survey list table showing all surveys with their creation date and response count, a survey detail view showing question breakdown, and a response trend chart showing how many responses were collected per day or week. For the response trend chart, you need to use Qualtrics's Response Export API which is asynchronous: POST to /responseexports to start the export, poll GET /responseexports/{progressId} until status is 'complete', then download the file URL in the response. For charts, Recharts integrates easily with Next.js and works in Bolt's WebContainer preview. Use a BarChart for per-survey response counts and a LineChart for response trends over time. shadcn/ui's Card, Table, and Badge components provide clean layout containers for the survey data. For a quicker alternative to the full async export, the Qualtrics API provides a response counts summary at /surveys/{surveyId}/questions that includes summary statistics for each question. For NPS surveys, this returns distribution data (promoters, passives, detractors) directly without needing the full export. Use the summary endpoint first to build the dashboard quickly, then add the full export for drill-down views when needed. The API rate limit is 3,000 requests per day for standard accounts — cache your survey list and response counts on the server side and refresh them on a schedule rather than on every page visit.

Bolt.new Prompt

Build a Qualtrics analytics dashboard page at app/dashboard/surveys/page.tsx. Fetch surveys from /api/qualtrics/surveys using SWR or React Query. Display them in a shadcn/ui Table with columns: Survey Name, Status (active/inactive badge), Last Modified date, and a View Details button. When a user clicks View Details, show a slide-out panel with the survey's question count and an embedded iframe of the Qualtrics report if available. Add a summary row at the top showing total surveys, total active surveys, and total responses across all surveys. Use skeleton loading states while data loads. Include a Refresh button that revalidates the surveys query.

Paste this in Bolt.new chat

app/dashboard/surveys/page.tsx
1// app/dashboard/surveys/page.tsx
2'use client';
3
4import { useEffect, useState } from 'react';
5
6interface Survey {
7 id: string;
8 name: string;
9 lastModified: string;
10 isActive: boolean;
11}
12
13export default function SurveysDashboard() {
14 const [surveys, setSurveys] = useState<Survey[]>([]);
15 const [loading, setLoading] = useState(true);
16 const [error, setError] = useState('');
17
18 const fetchSurveys = async () => {
19 setLoading(true);
20 try {
21 const res = await fetch('/api/qualtrics/surveys');
22 if (!res.ok) throw new Error('Failed to fetch surveys');
23 const data = await res.json();
24 setSurveys(data.surveys);
25 } catch (err) {
26 setError(err instanceof Error ? err.message : 'An error occurred');
27 } finally {
28 setLoading(false);
29 }
30 };
31
32 useEffect(() => { fetchSurveys(); }, []);
33
34 if (loading) {
35 return (
36 <div className="p-8 max-w-5xl mx-auto">
37 <div className="h-8 bg-gray-200 rounded w-48 mb-6 animate-pulse" />
38 {[...Array(5)].map((_, i) => (
39 <div key={i} className="h-12 bg-gray-100 rounded mb-2 animate-pulse" />
40 ))}
41 </div>
42 );
43 }
44
45 if (error) {
46 return (
47 <div className="p-8 max-w-5xl mx-auto">
48 <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
49 Error: {error}
50 </div>
51 </div>
52 );
53 }
54
55 const activeSurveys = surveys.filter(s => s.isActive).length;
56
57 return (
58 <div className="p-8 max-w-5xl mx-auto">
59 <div className="flex items-center justify-between mb-6">
60 <h1 className="text-2xl font-bold">Qualtrics Surveys</h1>
61 <button
62 onClick={fetchSurveys}
63 className="text-sm bg-gray-100 hover:bg-gray-200 px-4 py-2 rounded-lg font-medium"
64 >
65 Refresh
66 </button>
67 </div>
68
69 <div className="grid grid-cols-3 gap-4 mb-8">
70 <div className="bg-white border rounded-xl p-4">
71 <div className="text-2xl font-bold">{surveys.length}</div>
72 <div className="text-sm text-gray-500">Total Surveys</div>
73 </div>
74 <div className="bg-white border rounded-xl p-4">
75 <div className="text-2xl font-bold text-green-600">{activeSurveys}</div>
76 <div className="text-sm text-gray-500">Active</div>
77 </div>
78 <div className="bg-white border rounded-xl p-4">
79 <div className="text-2xl font-bold text-gray-400">{surveys.length - activeSurveys}</div>
80 <div className="text-sm text-gray-500">Inactive</div>
81 </div>
82 </div>
83
84 <div className="bg-white border rounded-xl overflow-hidden">
85 <table className="w-full">
86 <thead className="bg-gray-50 border-b">
87 <tr>
88 <th className="text-left px-4 py-3 text-sm font-medium text-gray-600">Survey Name</th>
89 <th className="text-left px-4 py-3 text-sm font-medium text-gray-600">Status</th>
90 <th className="text-left px-4 py-3 text-sm font-medium text-gray-600">Last Modified</th>
91 </tr>
92 </thead>
93 <tbody>
94 {surveys.map(survey => (
95 <tr key={survey.id} className="border-b last:border-0 hover:bg-gray-50">
96 <td className="px-4 py-3 text-sm font-medium">{survey.name}</td>
97 <td className="px-4 py-3">
98 <span className={`text-xs px-2 py-1 rounded-full font-medium ${
99 survey.isActive
100 ? 'bg-green-100 text-green-700'
101 : 'bg-gray-100 text-gray-500'
102 }`}>
103 {survey.isActive ? 'Active' : 'Inactive'}
104 </span>
105 </td>
106 <td className="px-4 py-3 text-sm text-gray-500">
107 {new Date(survey.lastModified).toLocaleDateString()}
108 </td>
109 </tr>
110 ))}
111 </tbody>
112 </table>
113 </div>
114 </div>
115 );
116}

Pro tip: Cache your survey list with a short TTL (5-10 minutes) using Next.js Route Handlers with appropriate Cache-Control headers or revalidate settings. The Qualtrics API has a rate limit of 3,000 requests per day — frequent dashboard refreshes can exhaust this quickly in a busy team environment.

Expected result: The survey analytics dashboard loads and displays your Qualtrics surveys with their active status and modification dates. The summary tiles show total, active, and inactive survey counts.

4

Embed a Qualtrics Survey and Handle Webhooks After Deployment

For collecting survey responses inside your Bolt.new app, embed the Qualtrics survey directly in a page using Qualtrics's JavaScript embed snippet. This approach keeps users within your application rather than redirecting them to a standalone Qualtrics survey URL. Qualtrics provides an embed snippet from the survey distribution settings: go to Distributions → Anonymous Link → Get Embed Code, and copy the JavaScript snippet. In a React component, load this script using useEffect with a dynamically created script element. The Qualtrics embed script adds a survey widget or redirects the current page to the survey depending on your embed configuration. For a full-page survey experience, use an anonymous survey link in an iframe instead — this is often cleaner for React applications since it avoids the Qualtrics script interfering with React's DOM management. For real-time response notifications, Qualtrics supports webhooks (called 'Event Subscriptions' in the API) that POST to your endpoint when a survey is submitted. During development in Bolt's WebContainer, your preview URL is not publicly accessible, so Qualtrics's servers cannot reach it. You must deploy to Netlify or Bolt Cloud first to test webhook functionality. After deploying, register your webhook URL using the Qualtrics Event Subscriptions API: POST to /eventsubscriptions with your survey ID, the event you want to subscribe to (e.g., surveyengine.completeSurvey), and your webhook URL. Store the incoming response data in a Supabase table for later analysis. Alternatively, for a simpler polling approach without webhooks, create a scheduled Supabase Edge Function (or a cron-triggered Netlify Function) that calls your Qualtrics API route at regular intervals to fetch new responses since the last check and sync them to your database. This avoids the webhook infrastructure complexity and works well for non-real-time dashboards.

Bolt.new Prompt

Create a survey embed page at app/survey/page.tsx that shows a Qualtrics survey in an iframe. The iframe src should be constructed from the anonymous survey link with process.env.NEXT_PUBLIC_QUALTRICS_SURVEY_ID: https://{datacenter}.qualtrics.com/jfe/form/{surveyId}. Make the iframe full-height with min-h-[600px] and w-full styling. Add a header with the survey title and a brief instructions paragraph before the iframe. Also create a webhook handler at app/api/webhooks/qualtrics/route.ts that accepts POST requests, logs the incoming response data, and saves the response ID and submission time to a Supabase 'survey_responses' table. Return 200 immediately.

Paste this in Bolt.new chat

app/api/webhooks/qualtrics/route.ts
1// app/api/webhooks/qualtrics/route.ts
2import { NextResponse } from 'next/server';
3import { createClient } from '@supabase/supabase-js';
4
5// Qualtrics sends webhooks as application/x-www-form-urlencoded by default
6// Configure your event subscription to use JSON for cleaner handling
7export async function POST(request: Request) {
8 try {
9 const contentType = request.headers.get('content-type') || '';
10
11 let payload: Record<string, unknown>;
12 if (contentType.includes('application/json')) {
13 payload = await request.json();
14 } else {
15 // Handle form-encoded Qualtrics payloads
16 const formData = await request.formData();
17 payload = Object.fromEntries(formData.entries());
18 }
19
20 console.log('Qualtrics webhook received:', JSON.stringify(payload, null, 2));
21
22 // Save to Supabase for analysis
23 const supabase = createClient(
24 process.env.NEXT_PUBLIC_SUPABASE_URL!,
25 process.env.SUPABASE_SERVICE_ROLE_KEY! // Use service role to bypass RLS
26 );
27
28 const { error } = await supabase.from('survey_responses').insert({
29 survey_id: payload.SurveyID || payload.surveyId,
30 response_id: payload.ResponseID || payload.responseId,
31 submitted_at: new Date().toISOString(),
32 raw_payload: payload,
33 });
34
35 if (error) console.error('Supabase insert error:', error);
36
37 // Always return 200 quickly — Qualtrics will retry on non-200 responses
38 return NextResponse.json({ received: true });
39 } catch (error) {
40 console.error('Webhook processing error:', error);
41 // Still return 200 to prevent Qualtrics from retrying
42 return NextResponse.json({ received: true });
43 }
44}

Pro tip: Qualtrics sends webhook payloads as form-encoded data by default. When registering your event subscription via the API, you can configure the format — request JSON format for easier handling. Add topics: ['surveyengine.completeSurvey'] to subscribe to completed surveys.

Expected result: The survey embed page shows the Qualtrics survey in an iframe. After deploying to Netlify or Bolt Cloud and registering the webhook URL in Qualtrics, survey completions are received by the webhook handler and saved to Supabase.

Common use cases

Survey Response Analytics Dashboard

Build a real-time analytics dashboard in Bolt.new that pulls response data from Qualtrics surveys, calculates completion rates and average scores, and visualizes results with charts. Perfect for research teams that need custom reporting beyond Qualtrics's built-in reports.

Bolt.new Prompt

Build a Qualtrics survey analytics dashboard in Next.js. Create an API route at app/api/qualtrics/surveys/route.ts that fetches all surveys from the Qualtrics API (GET https://{datacenter}.qualtrics.com/API/v3/surveys) using an Authorization header with the API token from process.env.QUALTRICS_API_TOKEN and datacenter from process.env.QUALTRICS_DATACENTER. Create another route app/api/qualtrics/surveys/[surveyId]/responses/route.ts that fetches response counts. Build a dashboard page at /surveys showing a table of surveys with name, creation date, question count, and response count. Add a bar chart using recharts showing responses per survey over the last 30 days.

Copy this prompt to try it in Bolt.new

Embedded Survey with Response Notifications

Embed a Qualtrics survey directly in a Bolt.new page so users can complete it without leaving the app, then trigger a notification or update a Supabase record when the survey is completed. Uses Qualtrics's JavaScript embed snippet for the survey display and a webhook for completion events.

Bolt.new Prompt

Add a Qualtrics survey embed to this Next.js app. Create a client component at components/QualtricsSurvey.tsx that uses useEffect to dynamically load the Qualtrics survey embed JavaScript snippet. The component should accept a surveyId prop and embed the survey using the Qualtrics Web Intercept or anonymous survey link approach. Show a loading state while the script loads and a thank-you message after completion (detected via the QSI.API.unload event). Style the container with a border and card layout using Tailwind.

Copy this prompt to try it in Bolt.new

Automated Survey Distribution for New Users

Automatically send a Qualtrics survey to new users when they complete onboarding in a Bolt.new app. Uses the Qualtrics Distributions API to create personalized survey links for each user and the Qualtrics Mailer to send them, triggered from a Supabase database webhook or an API route called during onboarding completion.

Bolt.new Prompt

Create an API route at app/api/qualtrics/distribute/route.ts that accepts POST requests with a user's email, first name, and last name. The route should call the Qualtrics Create Distribution endpoint (POST https://{datacenter}.qualtrics.com/API/v3/distributions) to generate a personalized survey invitation for the survey ID in process.env.QUALTRICS_SURVEY_ID using the mailing list ID from process.env.QUALTRICS_MAILING_LIST_ID. Include error handling for Qualtrics API rate limits. Return the generated survey link in the response so the frontend can display it immediately.

Copy this prompt to try it in Bolt.new

Troubleshooting

Qualtrics API returns 401 Unauthorized with the error 'API token not found'

Cause: The API token is missing or incorrectly set, the X-API-TOKEN header is misspelled, or the token was generated for a different Qualtrics account than the datacenter being used.

Solution: Verify the QUALTRICS_API_TOKEN and QUALTRICS_DATACENTER in your .env file match what is shown in your Qualtrics Account Settings → Qualtrics IDs. Tokens are account-specific — a token from account A will not work against account B's datacenter. Regenerate the token in Qualtrics if you are uncertain whether the current one is valid.

typescript
1// Verify the header name is exactly X-API-TOKEN (not Authorization or X-Qualtrics-Token)
2const res = await fetch(`https://${datacenter}.qualtrics.com/API/v3/surveys`, {
3 headers: {
4 'X-API-TOKEN': process.env.QUALTRICS_API_TOKEN!, // Must be server-side only
5 'Content-Type': 'application/json',
6 },
7});

Direct fetch to Qualtrics API from a React component returns a CORS error

Cause: Qualtrics's API does not allow cross-origin requests from browser clients. Direct calls from React components (client-side) are blocked by the browser's CORS policy.

Solution: All Qualtrics API calls must go through your Next.js API routes (server-side). Move the fetch call from your React component to an API route file in app/api/, and call your own API route from the React component instead.

typescript
1// WRONG: calling Qualtrics directly from React component
2const res = await fetch('https://iad1.qualtrics.com/API/v3/surveys');
3
4// CORRECT: call your own API route which proxies to Qualtrics
5const res = await fetch('/api/qualtrics/surveys');

Qualtrics webhooks are not received even after registering the webhook URL

Cause: The webhook URL was registered pointing to the Bolt WebContainer preview URL, which is not publicly accessible. Qualtrics cannot send HTTP POST requests to the browser-based development environment.

Solution: Deploy to Netlify or Bolt Cloud to get a public URL, then re-register the webhook with the deployed URL (e.g., https://your-app.netlify.app/api/webhooks/qualtrics). Use the Qualtrics Event Subscriptions API or the Qualtrics dashboard to update the endpoint URL.

Response export API returns progress as 'inProgress' indefinitely and never completes

Cause: The Qualtrics response export is asynchronous — you must poll the progress endpoint until the status is 'complete', then download the file from the provided URL. If polling stops early, the export appears stuck.

Solution: Implement proper polling with a maximum retry count. Poll every 2-3 seconds up to 30 times (60-90 seconds total). If still not complete, return an error and retry on the next request.

typescript
1async function exportResponses(surveyId: string, client: QualtricsClient) {
2 // Start export
3 const { result } = await client.request<{ result: { progressId: string } }>(
4 `/surveys/${surveyId}/export-responses`,
5 { method: 'POST', body: JSON.stringify({ format: 'json' }) }
6 );
7
8 // Poll for completion
9 let attempts = 0;
10 while (attempts < 30) {
11 await new Promise(r => setTimeout(r, 2000)); // wait 2 seconds
12 const { result: progress } = await client.request<{ result: { status: string; fileId?: string } }>(
13 `/surveys/${surveyId}/export-responses/${result.progressId}`
14 );
15 if (progress.status === 'complete' && progress.fileId) {
16 return client.request(`/surveys/${surveyId}/export-responses/${result.progressId}/file`);
17 }
18 attempts++;
19 }
20 throw new Error('Export timed out');
21}

Best practices

  • Cache Qualtrics survey lists and response counts in memory or Supabase for 5-10 minutes — the Qualtrics API has a 3,000 requests/day limit that analytics dashboards can exhaust quickly
  • Use the response counts summary endpoint instead of the full export API for dashboard metrics — it returns aggregate data instantly without the async export process
  • Store the QUALTRICS_API_TOKEN as a server-side-only environment variable (no NEXT_PUBLIC_ prefix) since it grants full account access and must never appear in client-side code
  • Deploy to Netlify or Bolt Cloud before testing webhook integrations — Qualtrics webhooks cannot reach the Bolt WebContainer development environment
  • For production survey embeds, use anonymous survey links in iframes rather than the JavaScript embed snippet — the iframe approach avoids potential conflicts between Qualtrics's script and React's virtual DOM
  • Add request logging to your Qualtrics proxy routes during development to debug API responses — Qualtrics error messages are informative but only visible server-side
  • When building for multiple client organizations using Qualtrics, store API tokens in a database table with organization IDs rather than as static environment variables

Alternatives

Frequently asked questions

Does Qualtrics have a free tier for API access?

No. Qualtrics does not offer a free tier — all plans are enterprise-priced, typically starting around $1,500/year for basic accounts. If you need a free or low-cost survey API for development, Typeform offers a free plan with 10 responses/month and a $25/month paid plan with API access and webhooks. SurveyMonkey also has a $25/month plan with API access.

How do I find my Qualtrics datacenter ID?

Log into Qualtrics, click your account name in the top right, select Account Settings, and look for the Qualtrics IDs section. Your datacenter ID is shown there (e.g., 'iad1', 'fra1', 'syd1'). It is also visible in your Qualtrics URL — the subdomain of your Qualtrics platform URL (https://yourorg.qualtrics.com shows 'yourorg' but the API datacenter may differ). Check Account Settings → Qualtrics IDs for the exact value.

Can I embed a Qualtrics survey in a Bolt.new app without the JavaScript snippet?

Yes. The simplest approach is using an anonymous survey link in an iframe. Go to Distributions → Anonymous Link in your Qualtrics survey, copy the survey URL, and add it as the iframe src in your React component. This approach is clean, avoids JavaScript snippet conflicts with React, and works in both the Bolt preview and after deployment. The main limitation is that the iframe cannot communicate with your React app when the survey completes.

Why does the Qualtrics response export take so long?

Qualtrics processes response exports asynchronously on their servers. For small surveys with a few hundred responses, the export typically completes in 5-15 seconds. For large surveys with thousands of responses or complex data, it can take 1-2 minutes. This is why the export API uses a polling pattern: start the export, poll the progress endpoint every 2-3 seconds, and download the file URL once it is complete. For dashboard aggregate metrics, use the response counts summary endpoint instead — it returns instantly without export processing.

Can Qualtrics webhooks be received during development in Bolt.new?

No. Qualtrics sends webhook events via HTTP POST requests to your registered endpoint. Bolt's WebContainer development environment runs inside a browser tab with no publicly accessible URL. Qualtrics's servers cannot reach it. Deploy to Netlify or Bolt Cloud to get a public URL, then register that URL as your webhook endpoint in Qualtrics's Event Subscriptions settings. Development typically requires testing webhook behavior against the deployed staging environment.

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.