# Close (/api/events/tickets/close)



## Event Type [#event-type]

```ts
"ticket.close"
```

## Description [#description]

This event is triggered when a ticket is closed by any means - staff command, button, auto-close, or other methods.

## Payload Structure [#payload-structure]

<TypeTable
  type="{
  ticketId: {
    description: 'Unique identifier for the ticket',
    type: 'string',
  },
  panelId: {
    description: 'ID of the panel the ticket belongs to',
    type: 'string',
  },
  channel: {
    description: 'Discord channel information',
    type: '{ id: string; name: string; }',
  },
  creatorId: {
    description: 'Discord user ID of who created the ticket',
    type: 'string',
  },
  closerId: {
    description: 'Discord user ID of who closed the ticket',
    type: 'string',
  },
  closeReason: {
    description: 'Reason provided for closing (empty string if none)',
    type: 'string',
  },
  openDate: {
    description: 'ISO 8601 timestamp when the ticket was created',
    type: 'string',
  },
  closeDate: {
    description: 'ISO 8601 timestamp when the ticket was closed',
    type: 'string',
  },
  transcript: {
    description: 'URL to the ticket transcript (if enabled)',
    type: 'string | undefined',
  },
  participants: {
    description: 'Everyone who sent at least one message in the ticket, with their message count and average response time. Sorted by message count, highest first. Empty for voided tickets',
    type: 'Participant[]',
  },
}"
/>

### Participant [#participant]

<TypeTable
  type="{
  id: {
    description: 'Discord user ID of the participant. Usernames are never included',
    type: 'string',
  },
  messages: {
    description: 'Number of messages this participant sent in the ticket',
    type: 'number',
  },
  averageResponseTime: {
    description: 'Average time in seconds this participant took to reply to the other side of the conversation, rounded to 2 decimals. Null when they never replied',
    type: 'number | null',
  },
  staff: {
    description: 'True for staff, false for the ticket creator and anyone added to the ticket',
    type: 'boolean',
  },
}"
/>

Bots are excluded, so messages from Tickety itself and from other bots never appear.

Response times are measured per side of the conversation. A staff member's response time is the gap between the last message from a ticket user and their reply, with the first one measured from the moment the ticket was opened. A ticket user's response time is the gap between the last staff message and their reply, so their opening message has nothing to measure against. Only the first reply after the other side speaks counts, so a burst of follow up messages does not lower the average.

## Example Payload [#example-payload]

```json
{
  "type": "ticket.close",
  "payload": {
    "ticketId": "TKT-1234",
    "panelId": "support-panel",
    "channel": {
      "id": "1140972530400776294",
      "name": "ticket-0001"
    },
    "creatorId": "713115896805064856",
    "closerId": "280158289667555328",
    "closeReason": "Issue resolved!",
    "openDate": "2024-01-18T15:30:00.000Z",
    "closeDate": "2024-01-18T16:45:00.000Z",
    "transcript": "https://tickety.top/transcripts/GdaWKdWF6A0m1Hs7W9P",
    "participants": [
      {
        "id": "713115896805064856",
        "messages": 14,
        "averageResponseTime": 182.4,
        "staff": false
      },
      {
        "id": "280158289667555328",
        "messages": 9,
        "averageResponseTime": 46.75,
        "staff": true
      },
      {
        "id": "451230862009171968",
        "messages": 1,
        "averageResponseTime": null,
        "staff": true
      }
    ]
  }
}
```

## Example Usage [#example-usage]

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

  if (type === 'ticket.close') {
    const duration = new Date(payload.closeDate) - new Date(payload.openDate);
    const hours = Math.floor(duration / (1000 * 60 * 60));
    
    console.log(`Ticket ${payload.ticketId} closed`);
    console.log(`Duration: ${hours} hours`);
    console.log(`Reason: ${payload.closeReason || 'No reason provided'}`);
    
    if (payload.transcript) {
      console.log(`Transcript: ${payload.transcript}`);
      // Archive transcript to your system
      await archiveTranscript(payload.transcript, payload.ticketId);
    }

    // Record how much each staff member handled and how fast they replied
    for (const participant of payload.participants.filter(p => p.staff)) {
      console.log(`${participant.id}: ${participant.messages} messages`);
      await recordStaffActivity({
        userId: participant.id,
        ticketId: payload.ticketId,
        messages: participant.messages,
        averageResponseTime: participant.averageResponseTime,
      });
    }
    
    // Update ticket status in your database
    await updateTicketStatus(payload.ticketId, 'closed');
  }

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

## Use Cases [#use-cases]

* Archive transcripts to your own storage
* Calculate ticket resolution times
* Update ticket status in external systems
* Send closure notifications
* Generate analytics on close reasons
* Track staff workload and response speed per ticket
* Spot tickets where the creator went quiet or replied slowly
