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

# Inbound Platform Events

> Receive reactions, edits, deletions, member changes, and thread creations from bot platforms

Inbound platform events let your agent hear more than text — reactions, edits, deletions, member changes, and thread creations from Discord, Telegram, Slack, and other supported platforms.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Inbound Platform Events"
        U[👤 User reacts / edits / joins] --> P[🔌 Platform SDK]
        P --> N[⚙️ Adapter normalises]
        N --> Fire[🪝 fire_platform_event]
        Fire --> Hook[✅ Your Hook]
    end

    classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef sdk fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef process fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff

    class U user
    class P sdk
    class N,Fire process
    class Hook result
```

## Quick Start

Register a hook on the agent, then opt the channel in from the gateway config.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.REACTION_RECEIVED)
def on_reaction(evt):
    if evt.emoji == "✅" and evt.kind == "reaction_added":
        print(f"{evt.user_id} approved message {evt.message_id}")

agent = Agent(name="ApprovalBot", instructions="Approve requests reacted to with ✅.")
agent.start("Approve incoming requests when a teammate reacts with ✅.")
```

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# gateway.yaml — opt in per channel
channels:
  my-discord-bot:
    platform: discord
    token: "${DISCORD_BOT_TOKEN}"
    events: [reactions]      # NEW — opt in to inbound reactions
```

> [!NOTE]
>
> Nothing new fires unless a channel opts in via `events:`. Omit the key and behaviour is unchanged — the surface is purely additive and capability-gated.

***

## Opt-In Classes

**1. reactions**

Hear when a user adds or removes a reaction. Branch on `evt.kind` to tell add from remove.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
channels:
  my-discord-bot:
    platform: discord
    token: "${DISCORD_BOT_TOKEN}"
    events: [reactions]
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.REACTION_RECEIVED)
def on_reaction(evt):
    if evt.kind == "reaction_added":
        print(f"{evt.user_id} added {evt.emoji} to {evt.message_id}")
    else:
        print(f"{evt.user_id} removed {evt.emoji} from {evt.message_id}")
```

**2. edits**

Hear message edits and deletions.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
channels:
  my-discord-bot:
    platform: discord
    token: "${DISCORD_BOT_TOKEN}"
    events: [edits]
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.MESSAGE_EDITED)
def on_edit(evt):
    print(f"{evt.message_id} is now: {evt.new_text}")

@add_hook(HookEvent.MESSAGE_DELETED)
def on_delete(evt):
    print(f"{evt.user_id} deleted {evt.message_id}")
```

**3. members**

Hear when a user joins or leaves. Requires the privileged `members` gateway intent, which the adapter enables only when you request this class.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
channels:
  my-discord-bot:
    platform: discord
    token: "${DISCORD_BOT_TOKEN}"
    events: [members]
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.MEMBER_JOINED)
def on_join(evt):
    print(f"Welcome {evt.user_id} to {evt.chat_id}")

@add_hook(HookEvent.MEMBER_LEFT)
def on_leave(evt):
    print(f"{evt.user_id} left {evt.chat_id}")
```

**4. threads**

Hear when a new thread is created. `thread_id` and the parent `chat_id` are populated.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
channels:
  my-discord-bot:
    platform: discord
    token: "${DISCORD_BOT_TOKEN}"
    events: [threads]
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.THREAD_CREATED)
def on_thread(evt):
    print(f"New thread {evt.thread_id} in {evt.chat_id}")
```

> [!TIP]
>
> Combine classes in one list: `events: [reactions, edits, members, threads]`.

***

## How It Works

A native platform event is normalised into a portable `PlatformEvent`, which fires the matching `HookEvent`.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Discord as Discord SDK
    participant Adapter as DiscordBot
    participant Hook as REACTION_RECEIVED Hook
    participant Agent

    User->>Discord: React ✅ on message m1
    Discord->>Adapter: on_raw_reaction_add(payload)
    Adapter->>Adapter: Skip if payload.user_id == bot.id
    Adapter->>Adapter: Build PlatformEvent(kind="reaction_added", …)
    Adapter->>Hook: fire_platform_event(evt)
    Hook->>Agent: agent.start("Approved: ...")
    Agent-->>User: Acknowledgement
```

Every adapter emits through one DRY seam, `fire_platform_event`, which maps `PlatformEvent.kind` onto the matching `HookEvent`. Reaction add and remove **both** fire `REACTION_RECEIVED` — the `kind` field disambiguates.

***

## Which Event Fires When?

Pick the class that matches what you want to hear.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Q{What do you want to hear?} -->|User reactions 👍/✅| A1[events: reactions → REACTION_RECEIVED]
    Q -->|User edits / deletes a message| A2[events: edits → MESSAGE_EDITED + MESSAGE_DELETED]
    Q -->|Join / leave the channel| A3[events: members → MEMBER_JOINED + MEMBER_LEFT]
    Q -->|New thread created| A4[events: threads → THREAD_CREATED]

    classDef q fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef opt fill:#189AB4,stroke:#7C90A0,color:#fff
    class Q q
    class A1,A2,A3,A4 opt
```

***

## Configuration Options

Each class token subscribes the adapter to a set of native handlers.

| Class token | Enabled events | Native handlers | Notes |
| - | - | - | - |
| `reactions` | `REACTION_RECEIVED` | `on_raw_reaction_add`, `on_raw_reaction_remove` | `kind` field disambiguates add vs remove; the bot's own reactions are ignored |
| `edits` | `MESSAGE_EDITED`, `MESSAGE_DELETED` | `on_message_edit`, `on_message_delete` | Bot-authored edits are skipped |
| `members` | `MEMBER_JOINED`, `MEMBER_LEFT` | `on_member_join`, `on_member_remove` | Requires the privileged **`members` gateway intent** — enabled only when this class is requested |
| `threads` | `THREAD_CREATED` | `on_thread_create` | `thread_id` and parent `chat_id` are populated |

### Hook Payload (`PlatformEventInput`)

Every inbound event carries a `PlatformEventInput`.

| Field | Type | Default | Description |
| - | - | - | - |
| `kind` | `str` | `""` | One of `reaction_added` / `reaction_removed` / `message_edited` / `message_deleted` / `member_joined` / `member_left` / `thread_created` |
| `platform` | `str` | `""` | Emitting platform (`"discord"` today; `"telegram"`/`"slack"` when their adapters wire it) |
| `chat_id` | `str` | `""` | Channel/chat where the event occurred |
| `user_id` | `str` | `""` | User who caused the event |
| `message_id` | `Optional[str]` | `None` | Target message id (reactions/edits/deletes) |
| `emoji` | `Optional[str]` | `None` | Reaction emoji, for reaction add/remove |
| `new_text` | `Optional[str]` | `None` | New content, for an edit (truncated to 500 chars in `to_dict()`) |
| `thread_id` | `Optional[str]` | `None` | Thread id, for thread creation |

Import it directly when you need the type:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import PlatformEventInput
from praisonaiagents.bots import PlatformEvent, PlatformEventKind
```

***

## Platform Coverage

Only Discord is wired today. Other adapters translate their native events into the same `PlatformEvent` in follow-up releases.

| Platform | Reactions | Edits | Deletes | Members | Threads |
| - | - | - | - | - | - |
| Discord | ✅ | ✅ | ✅ | ✅ (privileged intent) | ✅ |
| Telegram | ⏳ Not yet | ⏳ | ⏳ | ⏳ | ⏳ |
| Slack | ⏳ Not yet | ⏳ | ⏳ | ⏳ | ⏳ |
| WhatsApp | ⏳ Not yet | ⏳ | ⏳ | — | — |
| Others | See adapter | | | | |

> [!NOTE]
>
> Platforms that cannot deliver a given event simply never emit — nothing raises, hooks that never fire cost nothing. Enabling `events:` on a platform that hasn't wired it yet is safe and forward-compatible.

***

## Common Patterns

**Reaction-as-approval** — react ✅ to approve, no typing required.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.REACTION_RECEIVED)
def approve(evt):
    if evt.emoji == "✅" and evt.kind == "reaction_added":
        print(f"Approved {evt.message_id} by {evt.user_id}")
```

**Undo-on-delete** — retract a stored plan when the source message disappears.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.MESSAGE_DELETED)
def undo(evt):
    print(f"Clearing follow-ups tied to {evt.message_id}")
```

**Welcome-on-join** — greet new members.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.MEMBER_JOINED)
def welcome(evt):
    print(f"Sending welcome to {evt.user_id} in {evt.chat_id}")
```

**Thread-scoped session** — open a new agent session tied to a thread.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.THREAD_CREATED)
def open_session(evt):
    print(f"Starting a session for thread {evt.thread_id}")
```

***

## Best Practices

> **Enable only the classes you need**
>
> Every unlisted class stays unsubscribed. `members` in particular pulls the privileged Discord `members` intent — request it only when you use it.

> **Safe to combine with status reactions**
>
> The bot's own reactions and edits are filtered out automatically, so internal ack/done housekeeping is never mistaken for a user reaction. You can enable `reactions` alongside [status reactions](/docs/features/bot-status-reactions.md) without loops.

> **Hook errors are non-fatal**
>
> A raised exception inside a hook is logged at debug and swallowed — it never breaks the adapter's event loop.

> **Route by kind inside REACTION\_RECEIVED**
>
> Reaction add and remove both fire `REACTION_RECEIVED`. Branch on `evt.kind` (`reaction_added` vs `reaction_removed`) to distinguish them.

> **Remember new\_text is truncated in to\_dict()**
>
> `PlatformEventInput.to_dict()` truncates `new_text` to 500 chars for observability. The in-memory `evt.new_text` attribute is not truncated.

***

## Related

> **[Hook Events](/docs/features/hook-events.md)**
>
> Full reference for every HookEvent member.

> **[Bot Status Reactions](/docs/features/bot-status-reactions.md)**
>
> The outbound counterpart — the bot adds emoji to show run state.

> **[Interactive Bot Actions](/docs/features/interactive-bot-actions.md)**
>
> Button and select-menu clicks — another interactive surface.

> **[Messaging Bots](/docs/features/messaging-bots.md)**
>
> Where the gateway config lives.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.