# Submit (/api/events/applications/submit)



## Event Type [#event-type]

```ts
"application.submit"
```

## Description [#description]

This event is triggered when a user successfully submits an application through an application panel.

## Payload Structure [#payload-structure]

<TypeTable
  type="{
  applicationId: {
    description: 'Unique identifier for the application',
    type: 'string',
  },
  panelId: {
    description: 'ID of the application panel',
    type: 'string',
  },
  submitterId: {
    description: 'Discord user ID of who submitted the application',
    type: 'string',
  },
  submitDate: {
    description: 'ISO 8601 timestamp when the application was submitted',
    type: 'string',
  },
  questions: {
    description: 'Submitted application questions and answers',
    type: `Array<{
question: string;
description: string | null;
required: boolean;
type: &#x22;TEXT&#x22; | &#x22;CHOICE&#x22; | &#x22;UPLOAD&#x22;;
choices: string[];
skipped: boolean;
answer: string | null;
answerRaw: string | null;
upload: UploadReference | null;
}>`,
  },
}"
/>

## Question Fields [#question-fields]

<TypeTable
  type="{
  question: {
    description: 'The question shown to the applicant',
    type: 'string',
  },
  description: {
    description: 'Optional question description',
    type: 'string | null',
  },
  required: {
    description: 'Whether the question was required',
    type: 'boolean',
  },
  type: {
    description: 'Question type',
    type: '&#x22;TEXT&#x22; | &#x22;CHOICE&#x22; | &#x22;UPLOAD&#x22;',
  },
  choices: {
    description: 'Available choices for CHOICE questions',
    type: 'string[]',
  },
  skipped: {
    description: 'Whether the applicant skipped this question',
    type: 'boolean',
  },
  answer: {
    description: 'Human-readable submitted answer. For CHOICE questions, this contains the resolved choice label(s). For UPLOAD questions, this is the same URL as upload.url',
    type: 'string | null',
  },
  answerRaw: {
    description: 'Raw stored answer value. For CHOICE questions, this may contain selected option indexes. Always null for UPLOAD questions',
    type: 'string | null',
  },
  upload: {
    description: 'Present for UPLOAD questions with an answer. Fetch upload.url with your token to get the image. See the Uploads page',
    type: 'UploadReference | null',
  },
}"
/>

## Upload answers [#upload-answers]

Uploaded images are stored privately and have no permanent link. For an `UPLOAD` question, the
question carries an `upload` object which you exchange for the image using your HTTP events
token. See [Uploads](/api/uploads) for the endpoint, and note that `answerRaw` is `null` for
these questions because there is no raw text answer.

<TypeTable
  type="{
  scope: {
    description: 'Always &#x22;applications&#x22; for this event',
    type: '&#x22;applications&#x22;',
  },
  id: {
    description: 'The applicationId the upload was submitted with',
    type: 'string',
  },
  filename: {
    description: 'The stored filename',
    type: 'string',
  },
  url: {
    description: 'Call this with your token to fetch the image',
    type: 'string',
  },
}"
/>

## Example Payload [#example-payload]

```json
{
  "type": "application.submit",
  "payload": {
    "applicationId": "APP-5678",
    "panelId": "staff-application",
    "submitterId": "713115896805064856",
    "submitDate": "2024-01-18T15:30:00.000Z",
    "questions": [
      {
        "question": "What is your timezone?",
        "description": "Please include your usual availability.",
        "required": true,
        "type": "TEXT",
        "choices": [],
        "skipped": false,
        "answer": "CET, usually available after 6 PM",
        "answerRaw": "CET, usually available after 6 PM",
        "upload": null
      },
      {
        "question": "Which roles are you applying for?",
        "description": null,
        "required": true,
        "type": "CHOICE",
        "choices": ["Moderator", "Support", "Developer"],
        "skipped": false,
        "answer": "Moderator, Support",
        "answerRaw": "0 1",
        "upload": null
      },
      {
        "question": "Upload a screenshot of your setup",
        "description": null,
        "required": false,
        "type": "UPLOAD",
        "choices": [],
        "skipped": false,
        "answer": "https://tickety.top/api/uploads/applications/APP-5678/a1b2c3d4.webp",
        "answerRaw": null,
        "upload": {
          "scope": "applications",
          "id": "APP-5678",
          "filename": "a1b2c3d4.webp",
          "url": "https://tickety.top/api/uploads/applications/APP-5678/a1b2c3d4.webp"
        }
      }
    ]
  }
}
```

## Example Usage [#example-usage]

```ts
app.post("/webhook", async (req, res) => {
  const { type, payload } = req.body;

  if (type === "application.submit") {
    console.log("New application submitted!");
    console.log(`Application ID: ${payload.applicationId}`);
    console.log(`Panel: ${payload.panelId}`);
    console.log(`Submitter: ${payload.submitterId}`);
    console.log(`Questions: ${payload.questions.length}`);

    await storeApplication({
      id: payload.applicationId,
      panelId: payload.panelId,
      submitterId: payload.submitterId,
      submittedAt: payload.submitDate,
      status: "pending",
      questions: payload.questions,
    });

    const summary = payload.questions.map(question => ({
      question: question.question,
      answer: question.answer,
      skipped: question.skipped,
    }));

    await notifyApplicationReviewers(payload.panelId, {
      applicationId: payload.applicationId,
      submitterId: payload.submitterId,
      summary,
    });
  }

  res.status(200).send("OK");
});
```

## Use Cases [#use-cases]

* Track application submissions in your own database
* Send notifications to review teams
* Generate application metrics and analytics
* Integrate with external HR or management systems
* Automate initial application processing
* Store full question and answer snapshots for auditing
