> ## Documentation Index
> Fetch the complete documentation index at: https://docs.exoid.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Payload reference

> Complete reference of the JSON sent by Exoid: fields, TypeScript data model, answer types and an example.

Reference for the JSON body Exoid sends to your endpoint: fields, TypeScript data model and the shape of every answer type. You need it while writing your parser.

## Top-level fields

<ResponseField name="form_id" type="string">
  Interview identifier.
</ResponseField>

<ResponseField name="event_type" type="string">
  Event type. For interview responses it's always `form_response`.
</ResponseField>

<ResponseField name="event_id" type="string">
  Response identifier.
</ResponseField>

<ResponseField name="form_response" type="object">
  The full payload: interview definition, answers and metadata.
</ResponseField>

## Data model (TypeScript)

<AccordionGroup>
  <Accordion title="WebhookPayload and FormResponse" icon="braces">
    ```ts theme={"dark"}
    interface WebhookPayload {
      event_id: string; // Unique ID of the webhook event (response ID)
      event_type: "form_response"; // Always "form_response" for survey responses
      form_response: FormResponse; // The main response data
    }

    interface FormResponse {
      form_id: string; // Survey ID
      token: string; // Response token/ID
      submitted_at: string; // ISO 8601 UTC timestamp of when the response was submitted
      landed_at: string; // ISO 8601 UTC timestamp of when the user started the survey
      calculated?: {
        score?: number; // Calculated score
        [k: string]: unknown; // Additional calculated fields
      } | null;
      variables?: Variable[]; // Form variables coming from URL parameters
      hidden?: Record<string, string>; // Hidden fields (if present)
      definition: FormDefinition; // Definition of the survey structure
      answers: Answer[]; // User answers
      ending?: EndingRef; // Ending screen shown to the user
    }
    ```
  </Accordion>

  <Accordion title="FormDefinition and FieldDefinition" icon="list">
    ```ts theme={"dark"}
    interface FormDefinition {
      id: string; // Survey ID (same as form_id)
      title: string; // Survey title
      fields: FieldDefinition[]; // Survey questions/fields
      endings?: EndingDefinition[]; // Possible ending screens
    }

    interface FieldDefinition {
      id: string; // Unique field ID
      title: string; // Question text
      type: FieldType; // Question type (see below)
      ref?: string; // Optional reference identifier
      allow_multiple_selections?: boolean; // For multiple choice questions
      allow_other_choice?: boolean; // Whether the "Other" option is available
      choices?: ChoiceDefinition[]; // Available choices (for choice questions)
      properties?: Record<string, unknown>; // Additional field properties
    }
    ```
  </Accordion>

  <Accordion title="FieldType, ChoiceDefinition and Variable" icon="braces">
    ```ts theme={"dark"}
    type FieldType =
      | "short_text"
      | "long_text"
      | "email"
      | "number"
      | "picture_choice"
      | "single_choice"
      | "multiple_choice"
      | "yes_no"
      | "opinion_scale"
      | "rating"
      | "swipe"
      | "single_choice";

    interface ChoiceDefinition {
      id: string; // Choice ID
      label: string; // Choice text/label
      ref?: string; // Optional reference
    }

    interface Variable {
      key: string; // Variable name
      type: "text" | "number"; // Variable type
      text?: string; // Text value (if the type is "text")
      number?: number; // Numeric value (if the type is "number")
    }
    ```
  </Accordion>
</AccordionGroup>

## Supported answer types

Every answer contains a `field` object (`id`, `type`, `ref`) that links it back to the interview definition.

| Category | Field types                                 |
| -------- | ------------------------------------------- |
| Text     | `short_text`, `long_text`                   |
| Numeric  | `number`, `rating`, `opinion_scale`         |
| Choice   | `single_choice`, `multiple_choice`, `swipe` |
| Boolean  | `yes_no`                                    |

## Answer structure

The `type` field of each item in `answers` determines which property holds the value.

<AccordionGroup>
  <Accordion title="Text, boolean and number" icon="file-text">
    ```ts theme={"dark"}
    // Text Answer
    interface TextAnswer {
      type: "text";
      text: string;
      field: { id: string; type: FieldType; ref?: string };
    }

    // Boolean Answer
    interface BooleanAnswer {
      type: "boolean";
      boolean: boolean;
      field: FieldReference;
    }

    // Number Answer
    interface NumberAnswer {
      type: "number";
      number: number;
      field: FieldReference;
    }
    ```
  </Accordion>

  <Accordion title="Single choice and multiple choices" icon="list">
    ```ts theme={"dark"}
    // Single Choice Answer
    interface SingleChoiceAnswer {
      type: "choice";
      choice: {
        id?: string;
        label?: string;
        ref?: string;
        other?: string; // Custom text if "Other" was selected
      };
      field: FieldReference;
    }

    // Multiple Choices Answer
    interface MultipleChoicesAnswer {
      type: "choices";
      choices: {
        ids?: string[];
        labels?: string[];
        refs?: string[];
        other?: string;
      };
      field: FieldReference;
    }
    ```
  </Accordion>
</AccordionGroup>

## Hidden variables

Exoid captures the parameters passed in the interview URL (e.g. `?user_id=123`) and reports them in the payload under `variables`.

```json theme={"dark"}
"variables": [
  {
    "key": "user_id",
    "type": "text",
    "text": "1qd12-4e34e"
  }
]
```

Use them to correlate responses with users, sessions or internal campaigns.

## Full payload example

The interview in this example includes swipe, short text, opinion scale, yes/no, multiple choice and a hidden variable.

```json theme={"dark"}
{
  "event_id": "f851e006-15a0-42dd-8408-d96ca7425a7e",
  "event_type": "form_response",
  "form_response": {
    "form_id": "97b7c9c3-0d04-47db-afac-0459761b3e90",
    "token": "f851e006-15a0-42dd-8408-d96ca7425a7e",
    "submitted_at": "2025-10-01T17:28:52.136Z",
    "landed_at": "2025-10-01T17:28:19.893Z",
    "calculated": { "score": 0 },
    "definition": {
      "id": "97b7c9c3-0d04-47db-afac-0459761b3e90",
      "title": "as",
      "fields": [
        { "id": "node-1759153917681", "title": "Pick a your favorite social media app!", "type": "swipe", "ref": "node-1759153917681" },
        { "id": "node-1759337349635", "title": "What is your name?", "type": "short_text", "ref": "node-1759337349635" },
        { "id": "node-1759337403533", "title": "Rate us", "type": "opinion_scale", "ref": "node-1759337403533" },
        { "id": "node-1759337554850", "title": "Did you enjoy your day today? 🌅", "type": "yes_no", "ref": "node-1759337554850" },
        { "id": "node-1759337400490", "title": "What activities did you do today? 🌞", "type": "multiple_choice", "ref": "node-1759337400490" }
      ]
    },
    "answers": [
      { "field": { "id": "node-1759337349635", "type": "short_text", "ref": "node-1759337349635" }, "type": "text", "text": "Mario rossi" },
      { "field": { "id": "node-1759153917681", "type": "swipe", "ref": "node-1759153917681" }, "type": "choices", "choices": { "labels": ["facebook"], "ids": ["facebook"] } },
      { "field": { "id": "node-1759337403533", "type": "opinion_scale", "ref": "node-1759337403533" }, "type": "number", "number": 3 },
      { "field": { "id": "node-1759337400490", "type": "multiple_choice", "ref": "node-1759337400490" }, "type": "choices", "choices": { "labels": ["🏃 Exercise or sports", "🎮 Gaming or hobbies", "💤 Resting or napping"], "ids": ["🏃 Exercise or sports", "🎮 Gaming or hobbies", "💤 Resting or napping"] } },
      { "field": { "id": "node-1759337554850", "type": "yes_no", "ref": "node-1759337554850" }, "type": "boolean", "boolean": true }
    ],
    "variables": [
      { "key": "user_id", "type": "text", "text": "1qd12-4e34e" }
    ]
  }
}
```

## Key points

* All answers live in `answers[]`, each linked to its `field`.
* **Swipe** returns `type: "choices"` with `labels` and `ids`.
* **Multiple choices** return arrays in `choices.labels` and `choices.ids`.
* **Boolean** returns a `boolean` property.
* **Opinion scale** and the other numeric questions use `type: "number"`.
* **Text** returns a `text` property.
* **Variables** capture parameters passed via URL (e.g. `user_id`).

## Next steps

<CardGroup cols={2}>
  <Card title="Signature and security" icon="shield" href="/en/webhooks/security">
    Verify the `X-Signature` header before processing the payload.
  </Card>

  <Card title="How they work" icon="webhook" href="/en/webhooks/introduction">
    Delivery flow, retries and permanent error codes.
  </Card>
</CardGroup>


## Related topics

- [Signature and security](/en/webhooks/security.md)
- [How they work](/en/webhooks/introduction.md)
- [Configuration](/en/webhooks/configuration.md)
