Event Reference
Every webhook event type and the exact payload TicketWave sends.
Event Reference
Six event types can be subscribed to per endpoint. Every request uses the same envelope — event, timestamp, guild_id and data — and only the data object differs.
| Event | Fires when |
|---|---|
ticket.created | A ticket is opened |
ticket.closed | A ticket is closed |
ticket.updated | Anything else changes on an open ticket |
message.sent | A member or staff writes in a ticket |
blacklist.added | A member is blacklisted |
blacklist.removed | A blacklist entry is lifted |
Fields that cannot be resolved are sent as null rather than being omitted — so you can rely on the keys existing. A username is null when Discord did not return the user.
Shared fields
Every ticket event carries these:
| Field | Type | Description |
|---|---|---|
ticket_id | string | The readable ticket id from your pattern, e.g. ticket-1042 |
ticket_num_id | number | The running ticket number, e.g. 1042 |
channel_id | string | The Discord channel of the ticket |
category | string | null | Category name, null if the ticket has none |
user | object | null | The ticket owner as { id, username } |
ticket.created
Sent right after the ticket channel exists.
{
"event": "ticket.created",
"timestamp": "2026-08-26T08:23:11.000Z",
"guild_id": "123456789012345678",
"data": {
"ticket_id": "ticket-1042",
"ticket_num_id": 1042,
"channel_id": "998877665544332211",
"category": "🤖 Support",
"user": { "id": "987654321098765432", "username": "Luna" },
"created_at": "2026-08-26T08:23:11.000Z"
}
}This fires when the ticket is opened, which is before the member has written anything. If you need the first message, listen for message.sent as well.
ticket.closed
{
"event": "ticket.closed",
"timestamp": "2026-08-26T07:45:02.000Z",
"guild_id": "123456789012345678",
"data": {
"ticket_id": "ticket-1041",
"ticket_num_id": 1041,
"channel_id": "998877665544332211",
"category": "💸 Payment",
"user": { "id": "987654321098765432", "username": "Luna" },
"closed_by": { "id": "112233445566778899", "username": "Staff_01" },
"reason": "Issue resolved",
"closed_at": "2026-08-26T07:45:02.000Z"
}
}| Field | Type | Description |
|---|---|---|
closed_by | object | Who closed it. Automatic closes report the bot |
reason | string | null | The close reason, null if none was given |
ticket.updated
The catch-all for changes to an open ticket. action tells you what changed, changes carries the details of that specific action.
{
"event": "ticket.updated",
"timestamp": "2026-08-26T14:02:19.000Z",
"guild_id": "123456789012345678",
"data": {
"ticket_id": "ticket-1038",
"ticket_num_id": 1038,
"channel_id": "998877665544332211",
"category": "🤖 Support",
"user": { "id": "987654321098765432", "username": "Luna" },
"action": "ticket_priority:add",
"changes": { "priority": "high" },
"reason": null,
"updated_by": { "id": "112233445566778899", "username": "Staff_01" }
}
}Possible action values
action | Meaning | changes contains |
|---|---|---|
ticket_claim:add | A staff member claimed the ticket | (empty) |
ticket_unclaim:add | The claim was released | (empty) |
ticket_priority:add | Priority changed | priority |
ticket_rename:add | The channel was renamed | new_name |
ticket_remind:add | A reminder was sent | (empty) |
ticket_feedback:add | The member rated the ticket | star_count, feedback |
ticket_schedule_close:add | A close was scheduled | duration, schedule_time |
ticket_request_close:add | Staff asked the member to close | duration, request_duration_time |
ticket_request_close_accept:add | The member accepted | (empty) |
ticket_request_close_deny:add | The member declined | (empty) |
ticket_close_cancel:add | A running close was cancelled | (empty) |
ticket_additional_access:add | A user or role was added to the ticket | entity_type, entity_id, reason |
ticket_additional_access:remove | A user or role was removed | entity_type, entity_id, reason |
Treat this list as open-ended. New actions are added as TicketWave grows, and they arrive as ticket.updated. Switch on the actions you care about and ignore the rest instead of throwing on unknown values.
Answering a ticket step does not produce an event. A ticket with many steps would otherwise flood your endpoint while the member is still filling in the form.
message.sent
Sent for every human message inside an open ticket. Messages from bots — including TicketWave itself — are skipped.
{
"event": "message.sent",
"timestamp": "2026-08-26T22:10:55.000Z",
"guild_id": "123456789012345678",
"data": {
"ticket_id": "ticket-1040",
"ticket_num_id": 1040,
"channel_id": "998877665544332211",
"message_id": "1234567890123456789",
"content": "Hello, how can I help you?",
"author": { "id": "112233445566778899", "username": "Staff_01" },
"is_staff": true,
"sent_at": "2026-08-26T22:10:55.000Z"
}
}| Field | Type | Description |
|---|---|---|
content | string | The raw message text. Empty for attachment-only messages |
author | object | { id, username } of the sender |
is_staff | boolean | true if the sender has a configured support role |
This is the highest volume event by a wide margin — a busy server produces one request per message. Subscribe to it only if you genuinely need message-level data, and make sure your receiver answers quickly.
blacklist.added
Not tied to a ticket, so this payload has no ticket_id.
{
"event": "blacklist.added",
"timestamp": "2026-08-26T18:33:47.000Z",
"guild_id": "123456789012345678",
"data": {
"user": { "id": "111223344556677889", "username": "BadActor" },
"reason": "Spam",
"added_by": { "id": "112233445566778899", "username": "Staff_01" }
}
}blacklist.removed
{
"event": "blacklist.removed",
"timestamp": "2026-08-26T18:40:12.000Z",
"guild_id": "123456789012345678",
"data": {
"user": { "id": "111223344556677889", "username": "BadActor" },
"removed_by": { "id": "112233445566778899", "username": "Staff_01" }
}
}Test events
The Send test event button delivers a real, signed request whose data is just:
{
"event": "ticket.created",
"timestamp": "2026-08-26T12:00:00.000Z",
"guild_id": "123456789012345678",
"data": { "test": true }
}The event is the first type the endpoint is subscribed to. Check for data.test if you want to skip test deliveries in production logic.
Next Steps
- Example Server (Handle these payloads in Express)
- Webhooks (Signing, retries and delivery history)
How is this guide?
