---
title: Events
description: Every event Server, Client, and Proxy emit, with their arguments.
---

`Server`, `Client`, and `Proxy` extend Node's `EventEmitter`. `Server` re-emits most `Client` events with the client prepended as the first argument, so you can listen in one place instead of subscribing per connection.

> **Warning**
>
> `on("<PacketName>", …)` is state-agnostic: it receives every incoming packet with that name, regardless of whether it was decoded in `handshake`, `status`, `login`, `config`, or `play`. For lifecycle handling, use the state-qualified form `on("<State>/<PacketName>", …)` so a packet with the same name in another state cannot reach the wrong handler. For example, `on("KeepAlive", …)` matches both configuration and play Keep Alive packets, while `on("Config/KeepAlive", …)` matches only the configuration packet.

## Server

| Event | Arguments | When |
| --- | --- | --- |
| `Server:Ready` | – | Listening socket is open. |
| `Server:Error` | `error` | The listening socket failed (port in use, and so on). |
| `Server:Close` | – | The listening socket closed. |
| `connection` | `client` | A TCP connection was accepted. Nothing has been parsed yet. |
| `packet` | `client, packet` | A packet was parsed and its automatic initialization responses completed, if enabled. Not emitted for failed initialization or handled automatic keep-alive replies. `packet` is `{ id, name, data }`. |
| `<PacketName>` | `client, data, packet` | Every packet with this name across all states, for example `server.on("KeepAlive", …)`. |
| `<State>/<PacketName>` | `client, data, packet` | The same packet filtered by the state in which it was decoded, for example `server.on("Config/KeepAlive", …)`. State names are `Handshake`, `Status`, `Login`, `Config`, and `Play`. |
| `login` | `client, session` | The client sent `LoginAcknowledged`; state is now `config`. |
| `playerJoin` | `client, session` | The client sent `FinishConfiguration`; state is now `play`. Fires once per connection. |
| `playerLeave` | `client` | A client that had joined disconnected. Fires before `disconnect`. |
| `disconnect` | `client` | Any client disconnected, joined or not. |
| `stateChange` | `client, { direction, from, to }` | Either direction changed state. |
| `versionRejected` | `client, protocol` | The handshake asked for a protocol not in `server.versions` with a login or transfer intent. A `LoginDisconnect` was sent and the socket ended. |
| `keepAlive` | `client, { keepAliveId, latency }` | A `KeepAlive` reply matched an outstanding ping. `latency` is in milliseconds. Only with `autoKeepAlive`. |
| `Client:Error` | `client, error` | A per-client failure: packet parse error, `autoInit` throwing, unexpected or timed-out keep-alive. The client is destroyed on keep-alive failures. |

`server.clients` is a `Set<Client>` of live connections.

## Client

### Packets

| Event | Arguments | When |
| --- | --- | --- |
| `packet` | `packet` | A packet was parsed. `packet` is `{ id, name, data }`. |
| `<PacketName>` | `data, packet` | Every packet with this name across all states, for example `client.on("KeepAlive", …)`. |
| `<State>/<PacketName>` | `data, packet` | The same packet filtered by the state in which it was decoded, for example `client.on("Config/KeepAlive", …)`. State names are `Handshake`, `Status`, `Login`, `Config`, and `Play`. |
| `packetSent` | `{ name, data }` | After `sendPacket` wrote to the socket. |
| `Packet:Error` | `error` | Incoming bytes could not be parsed. The connection stays open. |

### Lifecycle

| Event | Arguments | When |
| --- | --- | --- |
| `login` | `session` | `LoginAcknowledged` passed through; state is now `config`. |
| `playerJoin` | `session` | Entered `play` for the first time. |
| `stateChange` | `{ direction, from, to }` | `direction` is `"incoming"` or `"outgoing"`. |
| `compression` | `threshold` | `LoginCompression` set the threshold. `null` disables. |
| `version` | `protocol` | The packet tables switched to another built-in version after `Intension`. |
| `versionRejected` | `protocol` | Server side: the handshake protocol is not accepted and the connection was refused. |

### Socket

| Event | Arguments | When |
| --- | --- | --- |
| `Socket:Ready` | – | Connected (client side) or attached (server side). |
| `Socket:Data` | `buffer` | Raw bytes arrived, before parsing. |
| `Socket:End` | – | The peer half-closed. |
| `Socket:Close` | `hadError` | The socket is gone. |
| `Socket:Error` | `error` | Underlying `net.Socket` error. |

## Async listeners

`sendPacket` is async and event listeners are called synchronously, so a listener that awaits can reject after the emitter has moved on. Attach a `.catch`:

```ts
server.on("Play/PlayerAction", (client, data, packet) => {
  handle(client, data, packet).catch((error) => {
    console.error(error);
    client.destroy();
  });
});
```

Packets are dispatched in order, but a listener that awaits does not block the next packet from being parsed. If ordering between two handlers matters, serialize them yourself.

## Proxy

`Proxy` emits its own lifecycle events. Packet interception uses `proxy.Client.on(name, handler)` and `proxy.Server.on(name, handler)` instead; those are not listed here. See [Building a proxy](/docs/proxy).

| Event | Arguments | When |
| --- | --- | --- |
| `Proxy:Ready` | – | The player-facing listener is open. |
| `Proxy:Error` | `error` | The player-facing listener failed. |
| `Proxy:Close` | – | The player-facing listener closed. |
| `connection` | `player, upstream` | A player connected and an upstream `Client` was created. The upstream socket may not be ready yet. |
| `disconnect` | `player, upstream` | The player socket closed. The upstream connection is destroyed. |
| `Upstream:Error` | `player, error` | The upstream socket or packet parse failed. A socket error also destroys the player. |
| `Client:Error` | `player, error` | The player connection failed to parse a packet. |

`proxy.connections` is a `Map<Client, Client>` from player to upstream.

## `session`

`login` and `playerJoin` hand you `client.session`. Every field is optional until the matching packet has passed through:

```ts
interface ClientSession {
  protocol?: number;       // from Intension
  serverAddress?: string;  // from Intension
  serverPort?: number;     // from Intension
  username?: string;       // from Hello, then LoginFinished
  uuid?: string;           // from Hello, then LoginFinished
  profile?: unknown;       // gameProfile from LoginFinished
}
```
