Skip to main content
Technology & EngineeringForm Survey Services234 lines

Jotform

Integrate JotForm's REST API to create forms, retrieve submissions, process payments, manage widgets, and configure webhooks. Covers form CRUD, submission handling, payment gateway integration, and TypeScript client patterns.

Quick Summary30 lines
You are an expert at integrating JotForm via its REST API. You build and manage forms programmatically, retrieve and process submissions, handle payment integrations, and react to events through webhooks with type-safe TypeScript patterns.

## Key Points

- **Ignoring `payment.status`** on payment forms. Submission existence does not mean payment succeeded — always check the payment object.
- **Using question array index** instead of question ID or type to locate answers. Indices change when questions are reordered.
- **Fetching all submissions without pagination**. Forms with thousands of entries will time out or hit rate limits.
- **Storing JotForm data without the submission ID**. You need it for updates, deletions, and deduplication.
- Building form-driven workflows where submissions trigger backend processes (CRM updates, order fulfillment).
- Integrating payment collection forms with order management systems.
- Programmatically creating many similar forms from templates.
- Migrating or syncing JotForm submission data to external databases.
- Adding webhook-based automation to existing JotForm setups.

## Quick Example

```bash
JOTFORM_API_KEY=your-api-key
# Use EU endpoint if your account is in EU:
# JOTFORM_BASE_URL=https://eu-api.jotform.com
```

```typescript
await jotformFetch(`/form/${formId}/webhooks`, {
  method: "POST",
  body: new URLSearchParams({ webhookURL: "https://myapp.com/webhooks/jotform" }),
});
```
skilldb get form-survey-services-skills/jotformFull skill: 234 lines
Paste into your CLAUDE.md or agent config

JotForm API Skill

You are an expert at integrating JotForm via its REST API. You build and manage forms programmatically, retrieve and process submissions, handle payment integrations, and react to events through webhooks with type-safe TypeScript patterns.

Core Philosophy

Resource-Oriented API Design

JotForm's API is RESTful with clear resource hierarchies: users own forms, forms contain questions and submissions. Navigate this hierarchy consistently — always scope requests by form ID, and use query parameters for filtering rather than fetching everything client-side.

Submission as Source of Truth

Treat JotForm submissions as your canonical data source. Each submission has a unique ID, a status, and a created date. Use the submission endpoint for backfills and webhooks for real-time processing. Always store the JotForm submission ID to enable updates and lookups.

Payment-Aware Form Handling

JotForm natively integrates with payment gateways (Stripe, PayPal, Square). When processing payment forms, always check the payment object in submissions for transaction status before fulfilling orders. Never assume payment succeeded based on submission existence alone.

Setup

Configure API authentication:

const JOTFORM_API_KEY = process.env.JOTFORM_API_KEY;
const BASE_URL = "https://api.jotform.com";

async function jotformFetch<T>(path: string, init?: RequestInit): Promise<T> {
  const url = new URL(`${BASE_URL}${path}`);
  url.searchParams.set("apiKey", JOTFORM_API_KEY!);
  const res = await fetch(url.toString(), {
    ...init,
    headers: { "Content-Type": "application/json", ...init?.headers },
  });
  if (!res.ok) throw new Error(`JotForm ${res.status}: ${await res.text()}`);
  const json = await res.json();
  return json.content as T;
}

Environment variables:

JOTFORM_API_KEY=your-api-key
# Use EU endpoint if your account is in EU:
# JOTFORM_BASE_URL=https://eu-api.jotform.com

Key Patterns

Retrieve and Process Submissions

Do this — paginate through submissions with filtering:

interface JotFormSubmission {
  id: string;
  form_id: string;
  status: "ACTIVE" | "DELETED" | "OVERQUOTA";
  created_at: string;
  answers: Record<string, {
    name: string;
    text: string;
    answer: string | Record<string, string>;
    type: string;
    prettyFormat?: string;
  }>;
  payment?: {
    total: string;
    currency: string;
    status: "PAID" | "PENDING" | "FAILED";
    transactionId: string;
  };
}

async function getSubmissions(
  formId: string,
  opts: { offset?: number; limit?: number; status?: string } = {}
): Promise<JotFormSubmission[]> {
  const params = new URLSearchParams({
    offset: String(opts.offset ?? 0),
    limit: String(opts.limit ?? 100),
    ...(opts.status ? { "filter[status]": opts.status } : {}),
  });
  return jotformFetch<JotFormSubmission[]>(`/form/${formId}/submissions?${params}`);
}

// Usage
const activeSubmissions = await getSubmissions("240000000000", { status: "ACTIVE", limit: 50 });
for (const sub of activeSubmissions) {
  const email = Object.values(sub.answers).find((a) => a.type === "control_email")?.answer;
  console.log(`Submission ${sub.id}: ${email}`);
}

Not this — fetching all submissions without pagination or filtering, causing timeouts on high-volume forms.

Handle Payment Submissions

Do this — verify payment status before fulfillment:

async function processPaymentSubmission(submissionId: string): Promise<void> {
  const submission = await jotformFetch<JotFormSubmission>(`/submission/${submissionId}`);

  if (!submission.payment) {
    throw new Error("Not a payment submission");
  }

  switch (submission.payment.status) {
    case "PAID":
      await fulfillOrder({
        transactionId: submission.payment.transactionId,
        amount: parseFloat(submission.payment.total),
        currency: submission.payment.currency,
        answers: submission.answers,
      });
      break;
    case "PENDING":
      await markOrderPending(submissionId);
      break;
    case "FAILED":
      await notifyPaymentFailure(submissionId);
      break;
  }
}

Not this — treating every submission as a successful purchase without checking payment.status.

Create Forms with Questions Programmatically

Do this — build forms via the API with typed question properties:

interface JotFormQuestion {
  type: "control_email" | "control_textbox" | "control_dropdown" | "control_fullname" | "control_payment";
  text: string;
  name: string;
  required: "Yes" | "No";
  order: string;
  options?: string;  // pipe-separated for dropdowns
}

const formId = await jotformFetch<string>("/user/forms", {
  method: "POST",
  body: new URLSearchParams({
    "properties[title]": "Order Form",
    "questions[1][type]": "control_fullname",
    "questions[1][text]": "Full Name",
    "questions[1][name]": "fullName",
    "questions[1][required]": "Yes",
    "questions[1][order]": "1",
    "questions[2][type]": "control_email",
    "questions[2][text]": "Email",
    "questions[2][name]": "email",
    "questions[2][required]": "Yes",
    "questions[2][order]": "2",
    "questions[3][type]": "control_dropdown",
    "questions[3][text]": "Plan",
    "questions[3][name]": "plan",
    "questions[3][options]": "Starter|Pro|Enterprise",
    "questions[3][order]": "3",
  }),
});

Not this — manually creating forms in the UI when you need many similar forms; use the API for templated creation.

Common Patterns

Register a Webhook

await jotformFetch(`/form/${formId}/webhooks`, {
  method: "POST",
  body: new URLSearchParams({ webhookURL: "https://myapp.com/webhooks/jotform" }),
});

Get Form Questions

interface JotFormQuestionMap {
  [questionId: string]: JotFormQuestion & { qid: string };
}

const questions = await jotformFetch<JotFormQuestionMap>(`/form/${formId}/questions`);
const emailQuestion = Object.values(questions).find((q) => q.type === "control_email");

Update Submission Status

await jotformFetch(`/submission/${submissionId}`, {
  method: "POST",
  body: new URLSearchParams({ "submission[status]": "ACTIVE" }),
});

List User Forms with Search

interface JotFormMeta {
  id: string;
  title: string;
  status: "ENABLED" | "DISABLED" | "DELETED";
  count: string;  // submission count as string
  created_at: string;
}

const forms = await jotformFetch<JotFormMeta[]>(
  `/user/forms?filter={"status":"ENABLED"}&orderby=created_at`
);

Anti-Patterns

  • Ignoring payment.status on payment forms. Submission existence does not mean payment succeeded — always check the payment object.
  • Using question array index instead of question ID or type to locate answers. Indices change when questions are reordered.
  • Fetching all submissions without pagination. Forms with thousands of entries will time out or hit rate limits.
  • Storing JotForm data without the submission ID. You need it for updates, deletions, and deduplication.

When to Use

  • Building form-driven workflows where submissions trigger backend processes (CRM updates, order fulfillment).
  • Integrating payment collection forms with order management systems.
  • Programmatically creating many similar forms from templates.
  • Migrating or syncing JotForm submission data to external databases.
  • Adding webhook-based automation to existing JotForm setups.

Install this skill directly: skilldb add form-survey-services-skills

Get CLI access →

Related Skills

React Hook Form

Build performant React forms with React Hook Form using register, validation, controllers, and form state management. Covers uncontrolled inputs, schema validation with Zod, controlled components, and TypeScript integration.

Form Survey Services249L

Surveymonkey

Integrate SurveyMonkey's REST API to create surveys, collect responses via collectors, process results, and configure webhooks. Covers survey design, collector management, response analysis, and TypeScript client patterns.

Form Survey Services258L

Tally

Integrate Tally forms via its API and embed SDK to handle submissions, configure webhooks, apply conditional logic, and embed forms seamlessly. Covers REST endpoints, webhook payloads, and front-end embedding with TypeScript.

Form Survey Services240L

Typeform

Integrate Typeform APIs to create forms, retrieve responses, configure webhooks, set up logic jumps, and embed forms in web applications. Covers the Create API, Responses API, and front-end embed SDK with TypeScript patterns.

Form Survey Services246L

Zod

Define and validate data schemas with Zod for parsing, transforms, refinements, and form integration. Covers schema composition, type inference, custom error messages, and integration with React Hook Form and tRPC.

Form Survey Services287L

Formbricks

Integrate Formbricks open-source surveys for in-app, website, and link-based feedback collection. Covers the JavaScript SDK for targeted in-app surveys, REST API for response management, and self-hosting configuration with TypeScript.

Form Survey Services246L