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

# alterself Gateway Events: Complete Event Reference

> Complete reference of all 26 Discord gateway events dispatched by alterself, with handler argument types, trigger conditions, and code examples.

alterself translates every raw Discord gateway dispatch into a typed Python event that your handlers receive as structured model objects. You register handlers with the `@bot.on("EVENT_NAME")` decorator, and alterself automatically parses the incoming JSON payload into the appropriate type before calling your function. This page lists every supported event, the type your handler receives as its argument, and what triggers the event.

***

## Connection Events

Connection events fire during session lifecycle transitions. You receive them when your client first authenticates with the gateway or when a dropped session is successfully resumed.

| Event | Handler Arg | Description |
| - | - | - |
| `READY` | `User` : the logged-in user | Fired once when the session is established and the gateway has sent initial state. The argument is the `User` object representing your own account. |
| `RESUMED` | `None` | Fired after a dropped connection is reconnected and the missed event sequence replayed. No argument is passed to your handler. |

```python theme={null}
@bot.on("READY")
async def on_ready(me):
    print(f"Logged in as {me.display_name}")

@bot.on("RESUMED")
async def on_resumed():
    print("Session resumed.")
```

***

## Message Events

Message events cover the full lifecycle of a Discord message: creation, editing, deletion, bulk deletion, and reactions.

| Event | Handler Arg | Description |
| - | - | - |
| `MESSAGE_CREATE` | `Message` | A new message was sent in any channel your client can see. |
| `MESSAGE_UPDATE` | `Message \| None` | An existing message was edited. `None` if the message is not in the local cache. |
| `MESSAGE_DELETE` | `Message \| None` | A message was deleted. `None` if the message was not cached before deletion. |
| `MESSAGE_DELETE_BULK` | `list[Message \| None]` | Multiple messages were deleted at once. Each element is `None` for any message not found in cache. |
| `MESSAGE_REACTION_ADD` | `dict` | A reaction emoji was added to a message. |
| `MESSAGE_REACTION_REMOVE` | `dict` | A reaction emoji was removed from a message. |

```python theme={null}
@bot.on("MESSAGE_CREATE")
async def on_message(msg):
    if msg.content.lower() == "ping":
        await bot.http.send_message(msg.channel_id, content="pong")

@bot.on("MESSAGE_DELETE")
async def on_delete(msg):
    if msg is not None:
        print(f"Deleted: {msg.content}")
    else:
        print("An uncached message was deleted.")
```

<Note>
  `MESSAGE_UPDATE` and `MESSAGE_DELETE` pass `None` when the message was not present in alterself's cache at the time of the event. This is normal. Discord does not resend full message content in update or delete payloads, so alterself can only provide a model when the original message was cached from a prior `MESSAGE_CREATE`.
</Note>

***

## Guild Events

Guild events fire when servers become available, change, or when members join or leave.

| Event | Handler Arg | Description |
| - | - | - |
| `GUILD_CREATE` | `Guild` | A guild became available, either at startup or after an outage. |
| `GUILD_UPDATE` | `Guild \| None` | A guild's settings changed. `None` if the guild is not in cache. |
| `GUILD_DELETE` | `Guild \| None` | You left a guild, or it became unavailable due to an outage. `None` if the guild was not cached. |
| `GUILD_MEMBER_ADD` | `Member` | A new member joined a guild you are in. |
| `GUILD_MEMBER_REMOVE` | `Member \| None` | A member left or was removed from a guild. `None` if the member was not cached. |
| `GUILD_MEMBER_UPDATE` | `Member \| None` | A guild member's metadata changed (nickname, roles, etc.). `None` if not cached. |

```python theme={null}
@bot.on("GUILD_CREATE")
async def on_guild_available(guild):
    print(f"Guild ready: {guild.name} ({len(guild.channels)} channels)")

@bot.on("GUILD_MEMBER_ADD")
async def on_member_join(member):
    print(f"{member.display_name} joined guild {member.guild_id}")
```

***

## Channel Events

Channel events fire when channels or threads are created, modified, or deleted within guilds you are in.

| Event | Handler Arg | Description |
| - | - | - |
| `CHANNEL_CREATE` | `Channel` | A new channel was created in a guild. |
| `CHANNEL_UPDATE` | `Channel \| None` | A channel's settings changed. `None` if not cached. |
| `CHANNEL_DELETE` | `Channel \| None` | A channel was deleted. `None` if not cached. |
| `THREAD_CREATE` | `Channel` | A new thread was created. The thread is represented as a `Channel` with an appropriate `kind`. |
| `THREAD_UPDATE` | `Channel \| None` | A thread's metadata changed. `None` if not cached. |
| `THREAD_DELETE` | `Channel \| None` | A thread was deleted. `None` if not cached. |

```python theme={null}
@bot.on("CHANNEL_CREATE")
async def on_channel(channel):
    print(f"New channel: {channel.name} in guild {channel.guild_id}")

@bot.on("THREAD_CREATE")
async def on_thread(thread):
    print(f"Thread opened: {thread.name} (parent {thread.parent_id})")
```

***

## Presence and Typing Events

These events notify you of user activity: when someone starts typing in a channel, or when a user's presence (online status, activity) changes.

| Event | Handler Arg | Description |
| - | - | - |
| `TYPING_START` | `dict` | A user started typing in a channel. The dict contains `user_id`, `channel_id`, `guild_id`, and `timestamp`. |
| `PRESENCE_UPDATE` | `dict` | A user's presence or activity changed. The dict mirrors the raw Discord presence object, including `user`, `status`, `activities`, and `client_status`. |

```python theme={null}
@bot.on("TYPING_START")
async def on_typing(data):
    print(f"User {data['user_id']} is typing in {data['channel_id']}")
```

<Note>
  Presence and typing events are delivered as raw `dict` objects rather than typed models. Discord's presence payload is highly variable in structure depending on the activity type, and parsing it into a rigid model would discard information. Use dictionary access to extract the fields you need.
</Note>

***

## Relationship Events

Relationship events are unique to user accounts. Bots do not receive them. They fire when the friends list or incoming friend request state changes.

| Event | Handler Arg | Description |
| - | - | - |
| `RELATIONSHIP_ADD` | `dict` | A friend request was received, or a relationship was accepted. The dict includes `id` (user snowflake), `type`, and optionally `user`. |
| `RELATIONSHIP_REMOVE` | `dict` | A friendship was ended or a request was declined/cancelled. The dict includes `id` and `type`. |

```python theme={null}
@bot.on("RELATIONSHIP_ADD")
async def on_relationship(data):
    if data.get("type") == 1:
        print(f"Now friends with user {data['id']}")
```

<Warning>
  Relationship events are only dispatched to user (selfbot) accounts. They will never arrive for bot token sessions.
</Warning>

***

## Voice Events

Voice events fire when voice state changes, such as users joining or leaving voice channels, or when Discord assigns your client a voice server endpoint.

| Event | Handler Arg | Description |
| - | - | - |
| `VOICE_STATE_UPDATE` | `dict` | A user's voice state changed: they joined, moved, muted, deafened, or left a voice channel. |
| `VOICE_SERVER_UPDATE` | `dict` | Discord assigned a voice server to your session. Contains `token`, `guild_id`, and `endpoint`. |

```python theme={null}
@bot.on("VOICE_STATE_UPDATE")
async def on_voice(data):
    if data.get("channel_id") is None:
        print(f"User {data['user_id']} left voice")
    else:
        print(f"User {data['user_id']} joined {data['channel_id']}")
```

***

## Raw Event Access

Every gateway dispatch, including events not listed above, is accessible by passing its exact name to `@bot.listen`. You receive the raw unparsed `dict` payload exactly as Discord sends it.

```python theme={null}
@bot.listen("USER_UPDATE")
async def raw_user_update(data: dict):
    print(data)
```

Use `@bot.listen` when you need access to fields that alterself's model layer does not expose, or when you are handling an event type that alterself does not yet parse into a typed object.

<Note>
  Handlers that accept no parameters are completely valid. alterself inspects your handler's signature at registration time and omits the argument if your function declares none. This is particularly useful for `RESUMED`, where there is no meaningful payload to act on.

  ```python theme={null}
  @bot.on("READY")
  async def on_ready():
      print("Ready!")   # No argument - perfectly fine
  ```
</Note>
