Typeform
Integrate Typeform APIs to create forms, retrieve responses, configure webhooks,
You are an expert at integrating the Typeform platform via its REST APIs and JavaScript Embed SDK. You build conversational forms programmatically, consume response data reliably, and react to submissions in real time through webhooks. ## Key Points - **Polling responses on a cron** instead of using webhooks. You waste API quota and introduce latency. - **Ignoring `event_id` uniqueness** in webhooks. Typeform may retry delivery, causing duplicate processing. - **Hardcoding field IDs** instead of using `ref` strings. Typeform auto-generates IDs that change when forms are duplicated. - **Embedding with raw iframes** instead of the Embed SDK. You lose `onSubmit` callbacks, hidden fields, and responsive sizing. - Building branded, conversational surveys where completion rate matters more than density. - Creating dynamic feedback forms that branch based on user sentiment or role. - Collecting lead information with conditional follow-up questions. - Embedding interactive forms in marketing landing pages or single-page apps. - Processing high-volume survey responses in real time via webhook pipelines. ## Quick Example ```bash TYPEFORM_PERSONAL_ACCESS_TOKEN=tfp_... TYPEFORM_WEBHOOK_SECRET=your-webhook-secret # for signature verification ```
skilldb get form-survey-services-skills/TypeformFull skill: 247 linesTypeform API Skill
You are an expert at integrating the Typeform platform via its REST APIs and JavaScript Embed SDK. You build conversational forms programmatically, consume response data reliably, and react to submissions in real time through webhooks.
Core Philosophy
Conversational-First Form Design
Typeform's power is one-question-at-a-time UX. Design forms that feel like conversations, not spreadsheets. Use logic jumps to branch paths based on answers so respondents only see relevant questions. Keep forms short — completion rates drop sharply after 10 questions.
Event-Driven Response Handling
Never poll the Responses API on a timer. Register webhooks to receive submissions the instant they complete. Use the webhook event_id for idempotent processing. Fall back to the Responses API only for historical backfills or reconciliation.
Type-Safe API Consumption
The Typeform API returns deeply nested JSON. Define TypeScript interfaces for every payload you consume — form definitions, response objects, and webhook events. Validate webhook signatures before processing to prevent spoofed submissions.
Setup
Install the HTTP client and configure authentication:
// No official Node SDK — use fetch or axios with personal access token
const TYPEFORM_TOKEN = process.env.TYPEFORM_PERSONAL_ACCESS_TOKEN;
const BASE_URL = "https://api.typeform.com";
async function typeformFetch<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE_URL}${path}`, {
...init,
headers: {
Authorization: `Bearer ${TYPEFORM_TOKEN}`,
"Content-Type": "application/json",
...init?.headers,
},
});
if (!res.ok) throw new Error(`Typeform ${res.status}: ${await res.text()}`);
return res.json() as Promise<T>;
}
Environment variables:
TYPEFORM_PERSONAL_ACCESS_TOKEN=tfp_...
TYPEFORM_WEBHOOK_SECRET=your-webhook-secret # for signature verification
Key Patterns
Create a Form with Logic Jumps
Do this — build forms programmatically with branching logic:
interface TypeformField {
ref: string;
type: "short_text" | "multiple_choice" | "yes_no" | "rating" | "email";
title: string;
properties?: Record<string, unknown>;
}
interface CreateFormPayload {
title: string;
fields: TypeformField[];
logic?: Array<{
type: "field";
ref: string;
actions: Array<{
action: "jump";
details: { to: { type: "field"; value: string } };
condition: { op: string; vars: Array<{ type: string; value: unknown }> };
}>;
}>;
}
const form = await typeformFetch<{ id: string }>("/forms", {
method: "POST",
body: JSON.stringify({
title: "Customer Feedback",
fields: [
{ ref: "satisfaction", type: "rating", title: "How satisfied are you?" },
{ ref: "why_low", type: "short_text", title: "What could we improve?" },
{ ref: "why_high", type: "short_text", title: "What did you love?" },
],
logic: [
{
type: "field",
ref: "satisfaction",
actions: [
{
action: "jump",
details: { to: { type: "field", value: "why_low" } },
condition: { op: "lower_than", vars: [{ type: "field", value: "satisfaction" }, { type: "constant", value: 3 }] },
},
{
action: "jump",
details: { to: { type: "field", value: "why_high" } },
condition: { op: "greater_equal_than", vars: [{ type: "field", value: "satisfaction" }, { type: "constant", value: 3 }] },
},
],
},
],
} satisfies CreateFormPayload),
});
Not this — creating all questions linearly without logic, forcing every respondent through irrelevant paths.
Process Webhook Submissions
Do this — verify the signature and handle idempotently:
import crypto from "node:crypto";
interface TypeformWebhookPayload {
event_id: string;
event_type: "form_response";
form_response: {
form_id: string;
token: string;
submitted_at: string;
answers: Array<{ field: { ref: string }; type: string; text?: string; number?: number; boolean?: boolean; choice?: { label: string } }>;
};
}
function verifySignature(payload: string, signature: string, secret: string): boolean {
const hash = crypto.createHmac("sha256", secret).update(payload).digest("base64");
return `sha256=${hash}` === signature;
}
// Express route handler
app.post("/webhooks/typeform", (req, res) => {
const sig = req.headers["typeform-signature"] as string;
if (!verifySignature(JSON.stringify(req.body), sig, process.env.TYPEFORM_WEBHOOK_SECRET!)) {
return res.status(403).send("Invalid signature");
}
const event = req.body as TypeformWebhookPayload;
// Use event_id for idempotent processing
await processSubmission(event.event_id, event.form_response);
res.status(200).send("OK");
});
Not this — skipping signature verification or relying on polling the Responses API every minute.
Embed Forms in React
Do this — use the official embed SDK with proper lifecycle management:
import { createWidget } from "@typeform/embed";
import "@typeform/embed/build/css/widget.css";
import { useEffect, useRef } from "react";
function TypeformEmbed({ formId, onSubmit }: { formId: string; onSubmit: (responseId: string) => void }) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const { unmount } = createWidget(formId, {
container: containerRef.current,
hidden: { source: "webapp" },
onSubmit: ({ responseId }) => onSubmit(responseId),
});
return () => unmount();
}, [formId, onSubmit]);
return <div ref={containerRef} style={{ height: 500 }} />;
}
Not this — embedding via raw iframe without the SDK, losing event callbacks and responsive sizing.
Common Patterns
Fetch Paginated Responses
interface ResponsesPage {
total_items: number;
page_count: number;
items: Array<{ response_id: string; submitted_at: string; answers: unknown[] }>;
}
async function fetchAllResponses(formId: string): Promise<ResponsesPage["items"]> {
const all: ResponsesPage["items"] = [];
let before: string | undefined;
do {
const params = new URLSearchParams({ page_size: "100" });
if (before) params.set("before", before);
const page = await typeformFetch<ResponsesPage>(`/forms/${formId}/responses?${params}`);
all.push(...page.items);
before = page.items.at(-1)?.submitted_at;
if (page.items.length < 100) break;
} while (true);
return all;
}
Register a Webhook
await typeformFetch(`/forms/${formId}/webhooks/my-hook`, {
method: "PUT",
body: JSON.stringify({
url: "https://myapp.com/webhooks/typeform",
enabled: true,
secret: process.env.TYPEFORM_WEBHOOK_SECRET,
}),
});
Retrieve a Single Form Definition
interface TypeformDefinition {
id: string;
title: string;
fields: TypeformField[];
_links: { display: string };
}
const definition = await typeformFetch<TypeformDefinition>(`/forms/${formId}`);
Anti-Patterns
- Polling responses on a cron instead of using webhooks. You waste API quota and introduce latency.
- Ignoring
event_iduniqueness in webhooks. Typeform may retry delivery, causing duplicate processing. - Hardcoding field IDs instead of using
refstrings. Typeform auto-generates IDs that change when forms are duplicated. - Embedding with raw iframes instead of the Embed SDK. You lose
onSubmitcallbacks, hidden fields, and responsive sizing.
When to Use
- Building branded, conversational surveys where completion rate matters more than density.
- Creating dynamic feedback forms that branch based on user sentiment or role.
- Collecting lead information with conditional follow-up questions.
- Embedding interactive forms in marketing landing pages or single-page apps.
- Processing high-volume survey responses in real time via webhook pipelines.
Install this skill directly: skilldb add form-survey-services-skills
Related Skills
Formbricks
Integrate Formbricks open-source surveys for in-app, website, and link-based
Formik
Build React forms with Formik using useFormik hook, Field components, and
Jotform
Integrate JotForm's REST API to create forms, retrieve submissions, process
React Hook Form
Build performant React forms with React Hook Form using register, validation,
Surveymonkey
Integrate SurveyMonkey's REST API to create surveys, collect responses via
Tally
Integrate Tally forms via its API and embed SDK to handle submissions, configure