Skip to content

Instantly share code, notes, and snippets.

@davidp-ro
Created August 31, 2024 08:41
/**
* Typeform API Typings since no official ones are provided for webhook data
*
* ---
*
* #### Relevant resources to view info about the API and Models:
*
* @see https://www.typeform.com/developers
* @see https://www.typeform.com/developers/webhooks/example-payload/
* @see https://www.typeform.com/developers/create/reference/create-form/
* @see https://www.typeform.com/developers/responses/JSON-response-explanation/
*/
export namespace Typeform {
/**
* Data received from a Typeform webhook.
*
* @see https://www.typeform.com/developers/webhooks/example-payload/
*/
export interface WebhookEvent {
/** Unique ID for the webhook. Automatically assigned by Typeform. */
event_id: string;
/** Reason the webhook is being sent. We care about `form_response`. */
event_type: string;
/**
* Object that contains information about the typeform and account
* associated with the webhook.
*
* @see FormResponse
*/
form_response: FormResponse;
}
/**
* Object that contains information about the typeform and account associated
* with the webhook.
*
* If someone opens the typeform but doesn't complete it, the payload will
* include an object that lists all the general data, with a `null` value
* for the answers array and no Hidden Fields or calculated score values!
*/
export interface FormResponse {
/**
* Unique ID for the typeform. Find in the form URL. For example, in the
* URL `https://mysite.typeform.com/to/u6nXL7`, the `form_id` is `u6nXL7`.
*/
form_id: string;
/**
* Unique ID for the typeform submission. This is identical to `response_id`
* in the Responses API.
*/
token: string;
/** Date and time the typeform responses were submitted, ISO Timestamp. */
submitted_at: string;
/** Date and time of form landing, ISO Timestamp. */
landed_at: string;
/**
* If the typeform includes a score calculation, the webhook response will
* contain this object.
*/
calculated?: {
/** Resulting score */
score: number;
};
/**
* If the typeform has variables, this array contains all the variables of
* the typeform and their values.
*
* @see Variable
*/
variables: Variable[];
/**
* If the typeform has hidden fields, the webhook response will contain an
* object with the value of each hidden field.
*/
hidden: {
[key: string]: string;
};
/**
* Object that lists the questions in the typeform. You can use the
* definition to match questions with answers and ending reference with
* ending screens.
*
* @see FormDefinition
*/
definition: FormDefinition;
/**
* An array of objects that lists the answers for the questions in the
* typeform.
*
* If someone opens the typeform but doesn't complete it, the payload will
* include an object that lists all the general data, with a `null` value
* for the answers array and no Hidden Fields or calculated score values!
*
* @see Answer
*/
answers: Answer[];
/**
* Typeform(s) finish with an Ending Screen, which appears after respondents
* click submit.
*
* A typeform can have multiple endings according to the form's logic.
* The webhook displays information under the `ending` field ending which
* contains the ID and the reference.
*
* You can match the entire ending using form definition.
*/
ending?: Ending;
}
/** Used variable */
export interface Variable {
/** Unique identifier for the variable. */
key: string;
/** Type of the variable, could either be `text` or `number`. */
type: string;
/** If `type == number`, contains the numerical value of the variable. */
number?: number;
/** If `type == text`, contains the text value of the variable. */
text?: string;
}
/**
* Object that lists the questions in the typeform. You can use the
* definition to match questions with answers and ending reference with ending
* screens.
*/
export interface FormDefinition {
/**
* Unique ID for the typeform. Find in the form URL. For example, in the
* URL `https://mysite.typeform.com/to/u6nXL7`, the `form_id` is `u6nXL7`.
*/
id: string;
/** Title of the typeform. */
title: string;
/**
* Questions in the typeform. Order of the fields in this questions array
* matches the order of fields in the answers array (which is included later
* in the payload).
*
* @see Field for the structure of each question.
*
* @see FormResponse.answers for the answers array.
* @see Answer for the structure of each answer.
*/
fields: Field[];
/**
* Form endings. The correct one can be matched using the values of the
* `FormResponse.ending` field.
*/
endings: Ending[];
}
/**
* Object that lists the questions in the typeform. You can use the
* definition to match questions with answers and ending reference with ending
* screens.
*
* @see FormDefinition.fields for the list of fields.
*
* @see FormResponse.answers for the answers array.
* @see Answer for the structure of each answer.
*/
export interface Field {
/**
* Unique ID for the field. You can use the field id to match questions with
* answers.
*/
id: string;
/** Title of the question associated with the field. */
title: string;
/**
* Question type.
*
* @remark If value is not in `SupportedFieldType`, then we may not support
* the answer value. Please check the `Answer` type before adding new values
* in `SupportedFieldType`.
*/
type: SupportedFieldType | {};
/**
* A name you can use to reference the field.
*
* When you create a form with the Create API, you can specify a readable ref
* for each field. If you don't specify a ref or you create the form through
* the Typeform admin panel, our system will generate a non-persistent ref
* for each field.
*
* The system-generated ref will look something like `0e1178a0-67f0-4779-...`
* and _**will be different in every payload**_.
*
* It must be less than 255 characters and in valid regular expression for
* `^[a-zA-Z0-9_-]+$`.
*/
ref?: string;
/**
* `true` if respondents can select more than one answer choice. `false` if
* respondents can select only one answer choice or the question is not a
* `multiple_choice` or `picture_choice` question type.
*/
allow_multiple_selections: boolean;
/**
* `true` if the question includes an "Other" option so respondents can enter
* a different answer choice from those listed. `false` if the question
* limits answer choices to those listed or the question is not a `multiple_choice`
* or `picture_choice` question type.
*/
allow_other_choice: boolean;
/**
* If the question type is `multiple_choice`, `picture_choice`, or `dropdown`,
* contains a list of available choices for this field.
*/
choices?: Choice[];
/** Used for fields like `calendly` that have properties. */
properties?: Record<string, any>;
}
/**
* Represents a choice in a `multiple_choice`, `picture_choice`, or `dropdown`
*/
export interface Choice {
/** Unique ID for the choice */
id: string;
/**
* Auto-generated identifier for the choice that can be changed
* programmatically.
*/
ref?: string;
/** Label for the choice */
label: string;
}
/**
* Answer to a question in a typeform.
*
* @remarks
* - this type will only expose the correct fields based on the `.type` field.
*
* - if you need to know the type of the parent question see `.field.type`.
*
* - it's **really important** to understand that `.type` and `.field.type`
* represent very different things:
* - `.type` represents the type of the answer, and can be used to extract
* the value from the submission's answer.
* - `.field.type` represents the type of the parent question, and can be
* used to match the question type to answer type, or show the question type
* in the UI, etc - NOT for matching to answer type!
*
* @see FormDefinition.fields for the list of fields.
*
* @see FormResponse.answers for the answers array.
*/
export type Answer =
| TextAnswer
| EmailAnswer
| PhoneNumberAnswer
| DateAnswer
| NumberAnswer
| BooleanAnswer
| ChoicesAnswer
| ChoiceAnswer
| UrlAnswer
| PaymentAnswer
| FileUrlAnswer;
/**
* Question type or the value of `[Answer].field.type`.
*
* @remark If value is not in `SupportedFieldType`, then we may not support
* the answer value. Please check the `Answer` type before adding new values
* in `SupportedFieldType`.
*/
export type SupportedFieldType =
| "short_text"
| "long_text"
| "dropdown"
| "multiple_choice"
| "picture_choice"
| "ranking"
| "email"
| "date"
| "legal"
| "yes_no"
| "website"
| "calendly"
| "rating"
| "opinion_scale"
| "number"
| "file_upload"
| "payment";
export interface AnswerField {
/**
* Unique ID for the field. You can use the field id to match questions with
* answers.
*/
id: string;
/**
* Type of the parent question.
*
* @remark If value is not in `SupportedFieldType`, then we may not support
* the answer value. Please check the `Answer` type before adding new values
* in `SupportedFieldType`.
*/
type: SupportedFieldType;
/* A name you can use to reference the field. */
ref?: string;
}
/** Base interface for all answer types to extend. */
interface BaseAnswer {
/**
* Object that contains identifying information to help you match the answer
* with the question.
*/
field: AnswerField;
}
/**
* Matching `field.type`:
* - `short_text`
* - `long_text`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface TextAnswer extends BaseAnswer {
type: "text";
text: string;
}
/**
* Matching `field.type`:
* - `email`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface EmailAnswer extends BaseAnswer {
type: "email";
email: string;
}
/**
* Matching `field.type`:
* - `phone_number`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface PhoneNumberAnswer extends BaseAnswer {
type: "phone_number";
phone_number: string;
}
/**
* Matching `field.type`:
* - `date`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface DateAnswer extends BaseAnswer {
type: "date";
/** Format is always `YYYY-MM-DD`. */
date: string;
}
/**
* Matching `field.type`:
* - `number`
* - `rating`
* - `opinion_scale`
* - `nps`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface NumberAnswer extends BaseAnswer {
type: "number";
number: number;
}
/**
* Matching `field.type`:
* - `yes_no`
* - `legal`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface BooleanAnswer extends BaseAnswer {
type: "boolean";
boolean: boolean;
}
/**
* Matching `field.type`:
* - `dropdown`
* - `multiple_choice`
* - `picture_choice`
*
* When the "multiple selections" option is activated.
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface ChoicesAnswer extends BaseAnswer {
type: "choices";
/**
* For multiple-selection choice fields, if respondents use the "Other" option
* to enter their own answers, the choices object will contain both `ids`,
* `labels`, `refs` of the selected choices and `other`.
*
* If respondents do not use the "Other" option or the "other" option is
* disabled (the typeform does not allow respondents to enter their own answers),
* choices will contain `ids`, `labels` and `refs`.
*/
choices: {
ids: string[];
labels: string[];
refs: string[];
other?: string;
};
}
/**
* Matching `field.type`:
* - `dropdown`
* - `multiple_choice`
* - `picture_choice`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface ChoiceAnswer extends BaseAnswer {
type: "choice";
/**
* For single-selection choice fields, the choice object contains either the
* selected choice properties (`id`, `label` and `ref`) or `other`.
*
* If the "Other" option is disabled (the typeform does not allow respondents
* to enter their own answers), choice will contain id, label and ref.
*/
choice: {
id?: string;
label?: string;
ref?: string;
other?: string;
};
}
/**
* Matching `field.type`:
* - `website`
* - `calendly`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface UrlAnswer extends BaseAnswer {
type: "url";
url: string;
}
/**
* Matching `field.type`:
* - `payment`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface PaymentAnswer extends BaseAnswer {
type: "payment";
payment: {
/**
* Amount of the payment. In the currency selected when the typeform was
* initially created.
*/
amount: number;
/** Last four digits of the credit card used to make the payment. */
last4: string;
/** Name on the credit card used to make the payment. */
name: string;
/** Whether the payment was processed successfully. */
success: boolean;
};
}
/**
* Matching `field.type`:
* - `file_upload`
*
* @see {@link Answer|Answer} Full Type definition & more details.
*/
export interface FileUrlAnswer extends BaseAnswer {
type: "file_url";
/**
* Note: Do not rely on file_url having a certain structure or shape - it is
* subject to change. To access file information for a file obtained using
* file_url from a webhook payload, you can rely on the Content-Disposition
* header.
*
* It is not currently possible to reliably access file information for a
* file obtained using a file_url from a Results API payload.
*/
file_url: string;
}
/**
* An ending screen in a Typeform. Can be matched using the `FormDefinition`.
*/
export interface Ending {
id: string;
ref: string;
title?: string;
type?: string;
properties?: {
button_text?: string;
show_button?: boolean;
share_icons?: boolean;
button_mode?: string;
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment