> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP and WebSockets

> Handle HTTP requests and WebSocket connections in your agents

Agents handle both HTTP requests and WebSocket connections, giving you flexibility to build REST APIs, real-time applications, or hybrid architectures.

## Overview

Every agent can respond to:

* **HTTP requests** via `onRequest()` - REST APIs, webhooks, file uploads
* **WebSocket connections** via `onConnect()`, `onMessage()`, `onClose()` - Real-time bidirectional communication

```typescript theme={null}
import { Agent } from "agents";

export class MyAgent extends Agent {
  // Handle HTTP requests
  onRequest(request: Request): Response {
    return new Response("Hello from HTTP!");
  }

  // Handle WebSocket connections
  onConnect(connection: Connection, ctx: ConnectionContext) {
    connection.send("Welcome!");
  }

  onMessage(connection: Connection, message: WSMessage) {
    // Echo back
    connection.send(message);
  }
}
```

## Lifecycle Hooks

Agents have several lifecycle hooks that are called at different points:

| Hook                                          | When Called                                               |
| --------------------------------------------- | --------------------------------------------------------- |
| `onStart(props?)`                             | Once when the agent first starts (before any connections) |
| `onRequest(request)`                          | When an HTTP request is received (non-WebSocket)          |
| `onConnect(connection, ctx)`                  | When a new WebSocket connection is established            |
| `onMessage(connection, message)`              | When a WebSocket message is received                      |
| `onClose(connection, code, reason, wasClean)` | When a WebSocket connection closes                        |
| `onError(connection, error)`                  | When a WebSocket error occurs                             |
| `onError(error)`                              | When a server error occurs (overload)                     |

### Lifecycle Flow

```
Agent Created
     ↓
  onStart()  ←── Called once, before any connections
     ↓
┌─────────────────────────────────────┐
│  For each request:                  │
│                                     │
│  HTTP Request ───→ onRequest()       │
│                                     │
│  WebSocket ───→ onConnect()          │
│       ↓                             │
│  Messages ───→ onMessage() (repeat)  │
│       ↓                             │
│  Disconnect ───→ onClose()           │
└─────────────────────────────────────┘
```

## HTTP Requests

Handle HTTP requests with `onRequest()`. This is called for any non-WebSocket request to your agent.

```typescript theme={null}
export class ApiAgent extends Agent {
  async onRequest(request: Request): Promise<Response> {
    const url = new URL(request.url);

    // Route by path
    if (url.pathname.endsWith("/status")) {
      return Response.json({
        status: "ok",
        connections: this.getConnections().length
      });
    }

    if (url.pathname.endsWith("/data") && request.method === "POST") {
      const data = await request.json();
      // Process data...
      return Response.json({ received: true });
    }

    return new Response("Not found", { status: 404 });
  }
}
```

### Common HTTP Patterns

<CodeGroup>
  ```typescript REST API with CORS theme={null}
  onRequest(request: Request): Response {
    // Handle preflight
    if (request.method === "OPTIONS") {
      return new Response(null, {
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
          "Access-Control-Allow-Headers": "Content-Type"
        }
      });
    }

    return Response.json(
      { data: "..." },
      { headers: { "Access-Control-Allow-Origin": "*" } }
    );
  }
  ```

  ```typescript File Upload theme={null}
  async onRequest(request: Request): Promise<Response> {
    if (request.method === "POST") {
      const formData = await request.formData();
      const file = formData.get("file") as File;

      // Process file...
      const content = await file.text();

      return Response.json({ filename: file.name, size: file.size });
    }
    return new Response("Method not allowed", { status: 405 });
  }
  ```
</CodeGroup>

## WebSocket Connections

WebSockets enable real-time bidirectional communication between clients and your agent.

### Connection Lifecycle

```typescript theme={null}
export class ChatAgent extends Agent {
  // Called when a client connects
  onConnect(connection: Connection, ctx: ConnectionContext) {
    // ctx.request contains the original HTTP request (for auth, headers, etc.)
    const url = new URL(ctx.request.url);
    const token = url.searchParams.get("token");

    console.log(`Client ${connection.id} connected`);
    connection.send(JSON.stringify({ type: "welcome", id: connection.id }));
  }

  // Called for each message from this connection
  onMessage(connection: Connection, message: WSMessage) {
    if (typeof message === "string") {
      const data = JSON.parse(message);
      // Handle message...
    } else {
      // Binary message (ArrayBuffer)
    }
  }

  // Called when connection closes
  onClose(
    connection: Connection,
    code: number,
    reason: string,
    wasClean: boolean
  ) {
    console.log(`Client ${connection.id} disconnected: ${code} ${reason}`);
  }

  // Called on WebSocket errors
  onError(connection: Connection, error: unknown) {
    console.error(`Error on connection ${connection.id}:`, error);
  }
}
```

### The Connection Object

Each WebSocket connection is represented by a `Connection` object:

<ParamField path="id" type="string">
  Unique connection identifier
</ParamField>

<ParamField path="server" type="string">
  The agent instance name this connection belongs to
</ParamField>

<ParamField path="state" type="TState | null">
  Per-connection state (read-only, use setState to update)
</ParamField>

<ParamField path="setState" type="(state: TState | ((prev: TState | null) => TState)) => void">
  Update connection state
</ParamField>

<ParamField path="send" type="(message: string | ArrayBuffer) => void">
  Send a message to this connection
</ParamField>

<ParamField path="close" type="(code?: number, reason?: string) => void">
  Close this connection
</ParamField>

### Message Types

Messages can be strings or binary:

```typescript theme={null}
onMessage(connection: Connection, message: WSMessage) {
  if (typeof message === "string") {
    // Text message - usually JSON
    const data = JSON.parse(message);
    this.handleTextMessage(connection, data);
  } else {
    // Binary message - ArrayBuffer or ArrayBufferView
    this.handleBinaryMessage(connection, message);
  }
}
```

## Connection Management

### Getting Connections

```typescript theme={null}
// Get all connections
const connections = this.getConnections();

// Get a specific connection by ID
const connection = this.getConnection("abc123");

// Get connections with a specific tag
const adminConnections = this.getConnections("admin");
```

### Broadcasting

Send a message to all connected clients:

```typescript theme={null}
// Broadcast to everyone
this.broadcast(JSON.stringify({ type: "update", data: "..." }));

// Broadcast to everyone except specific connections
this.broadcast(
  JSON.stringify({ type: "user-typing", userId: "123" }),
  ["connection-id-to-exclude"] // Don't send to the originator
);
```

### Connection Tags

Tag connections for easy filtering. Override `getConnectionTags()` to assign tags:

```typescript theme={null}
export class ChatAgent extends Agent {
  // Called when a connection is established
  getConnectionTags(connection: Connection, ctx: ConnectionContext): string[] {
    const url = new URL(ctx.request.url);
    const role = url.searchParams.get("role");

    const tags: string[] = [];
    if (role === "admin") tags.push("admin");
    if (role === "moderator") tags.push("moderator");

    return tags; // Up to 9 tags, max 256 chars each
  }

  // Later, broadcast only to admins
  notifyAdmins(message: string) {
    for (const conn of this.getConnections("admin")) {
      conn.send(message);
    }
  }
}
```

## Per-Connection State

Store data specific to each connection using `connection.state` and `connection.setState()`:

```typescript theme={null}
type ConnectionState = {
  username: string;
  joinedAt: number;
  messageCount: number;
};

export class ChatAgent extends Agent {
  onConnect(connection: Connection<ConnectionState>, ctx: ConnectionContext) {
    const url = new URL(ctx.request.url);

    // Initialize connection state
    connection.setState({
      username: url.searchParams.get("username") || "Anonymous",
      joinedAt: Date.now(),
      messageCount: 0
    });
  }

  onMessage(connection: Connection<ConnectionState>, message: WSMessage) {
    // Update message count using functional update
    connection.setState((prev) => ({
      ...prev!,
      messageCount: (prev?.messageCount || 0) + 1
    }));

    // Access current state
    const { username, messageCount } = connection.state!;
    console.log(`${username} sent message #${messageCount}`);
  }
}
```

<Note>
  Connection state is:

  * **Immutable** - Read via `connection.state`, update via `connection.setState()`
  * **Per-connection** - Each connection has its own state
  * **Persisted across hibernation** - Survives agent sleep/wake cycles
</Note>

## The onStart Hook

`onStart()` is called once when the agent first starts, before any connections are established:

```typescript theme={null}
export class MyAgent extends Agent {
  private cache: Map<string, unknown> = new Map();

  async onStart() {
    // Initialize resources
    console.log(`Agent ${this.name} starting...`);

    // Load data from storage
    const savedData = this.sql`SELECT * FROM cache`;
    for (const row of savedData) {
      this.cache.set(row.key, row.value);
    }

    // Restore MCP connections, check workflows, etc.
    // (Agent does this automatically, but you can add custom logic)
  }

  onConnect(connection: Connection) {
    // By the time connections arrive, onStart has completed
  }
}
```

## Protocol Message Control

By default, when a WebSocket client connects, the agent sends protocol text frames (`CF_AGENT_IDENTITY`, `CF_AGENT_STATE`, `CF_AGENT_MCP_SERVERS`) to keep the client in sync. You can suppress these on a per-connection basis by overriding `shouldSendProtocolMessages`:

```typescript theme={null}
export class MyAgent extends Agent {
  shouldSendProtocolMessages(
    connection: Connection,
    ctx: ConnectionContext
  ): boolean {
    // Suppress protocol frames for binary-only clients
    const url = new URL(ctx.request.url);
    return url.searchParams.get("protocol") !== "false";
  }
}
```

When `shouldSendProtocolMessages` returns `false` for a connection:

* No `CF_AGENT_IDENTITY`, `CF_AGENT_STATE`, or `CF_AGENT_MCP_SERVERS` frames are sent on connect
* The connection is excluded from protocol broadcasts (state updates, MCP server changes)
* Regular messages via `connection.send()` and `this.broadcast()` still work normally

This is useful for IoT devices, binary-only clients, or lightweight consumers that only need raw messages.

### Checking Protocol Status

Use `isConnectionProtocolEnabled` to check whether a connection receives protocol messages:

```typescript theme={null}
const enabled = this.isConnectionProtocolEnabled(connection);
```

This status persists across hibernation — a connection that was marked as no-protocol before hibernation remains no-protocol after waking up.

## Error Handling

Handle errors gracefully with `onError`:

```typescript theme={null}
export class MyAgent extends Agent {
  // WebSocket connection error
  onError(connection: Connection, error: unknown): void {
    console.error(`WebSocket error on ${connection.id}:`, error);
    connection.send(
      JSON.stringify({ type: "error", message: "Connection error" })
    );
  }

  // Server error (overloaded signature - no connection parameter)
  onError(error: unknown): void {
    console.error("Server error:", error);
    // Log to external service, etc.
  }
}
```

## Hibernation

Agents support hibernation - they can sleep when inactive and wake when needed. This saves resources while maintaining WebSocket connections.

### Enabling Hibernation

Hibernation is enabled by default. To disable:

```typescript theme={null}
export class AlwaysOnAgent extends Agent {
  static options = { hibernate: false };
}
```

### How Hibernation Works

<Steps>
  <Step title="Agent is active">
    Handling connections
  </Step>

  <Step title="After ~10 seconds of no messages">
    Agent hibernates (sleeps)
  </Step>

  <Step title="WebSocket connections remain open">
    Handled by Cloudflare
  </Step>

  <Step title="When a message arrives">
    Agent wakes up
  </Step>

  <Step title="onMessage is called">
    As normal
  </Step>
</Steps>

### What Persists Across Hibernation

| Persists                   | Does Not Persist    |
| -------------------------- | ------------------- |
| `this.state` (agent state) | In-memory variables |
| `connection.state`         | Timers/intervals    |
| SQLite data (`this.sql`)   | Promises in flight  |
| Connection metadata        | Local caches        |

<Warning>
  **Best Practice:** Store important data in `this.state` or SQLite, not in class properties:

  ```typescript theme={null}
  export class MyAgent extends Agent<Env, { counter: number }> {
    initialState = { counter: 0 };

    // ❌ Don't do this - lost on hibernation
    private localCounter = 0;

    onMessage(connection: Connection, message: WSMessage) {
      // ✅ Do this - persists
      this.setState({ counter: this.state.counter + 1 });

      // ❌ Lost after hibernation
      this.localCounter++;
    }
  }
  ```
</Warning>

## Common Patterns

### Authentication on Connect

Validate users when they connect:

```typescript theme={null}
export class SecureAgent extends Agent {
  async onConnect(connection: Connection, ctx: ConnectionContext) {
    const url = new URL(ctx.request.url);
    const token = url.searchParams.get("token");

    if (!token || !(await this.validateToken(token))) {
      connection.close(4001, "Unauthorized");
      return;
    }

    const user = await this.getUserFromToken(token);
    connection.setState({ userId: user.id, role: user.role });

    connection.send(JSON.stringify({ type: "authenticated", user }));
  }

  private async validateToken(token: string): Promise<boolean> {
    // Validate JWT, check database, etc.
    return true;
  }
}
```

### Chat Room with Broadcast

```typescript theme={null}
type Message = {
  type: "message" | "join" | "leave";
  user: string;
  text?: string;
  timestamp: number;
};

export class ChatRoom extends Agent {
  onConnect(connection: Connection, ctx: ConnectionContext) {
    const url = new URL(ctx.request.url);
    const username = url.searchParams.get("username") || "Anonymous";

    connection.setState({ username });

    // Notify others
    this.broadcast(
      JSON.stringify({
        type: "join",
        user: username,
        timestamp: Date.now()
      } satisfies Message),
      [connection.id] // Don't send to the joining user
    );
  }

  onMessage(connection: Connection, message: WSMessage) {
    if (typeof message !== "string") return;

    const { username } = connection.state as { username: string };

    // Broadcast to everyone
    this.broadcast(
      JSON.stringify({
        type: "message",
        user: username,
        text: message,
        timestamp: Date.now()
      } satisfies Message)
    );
  }

  onClose(connection: Connection) {
    const { username } = (connection.state as { username: string }) || {};
    if (username) {
      this.broadcast(
        JSON.stringify({
          type: "leave",
          user: username,
          timestamp: Date.now()
        } satisfies Message)
      );
    }
  }
}
```

### Presence Tracking

Track who's online using per-connection state. This pattern is clean because connection state is automatically cleaned up when users disconnect:

```typescript theme={null}
type UserState = {
  name: string;
  joinedAt: number;
  lastSeen: number;
};

export class PresenceAgent extends Agent {
  onConnect(connection: Connection<UserState>, ctx: ConnectionContext) {
    const url = new URL(ctx.request.url);
    const name = url.searchParams.get("name") || "Anonymous";

    // Store user data on the connection itself
    connection.setState({
      name,
      joinedAt: Date.now(),
      lastSeen: Date.now()
    });

    // Send current presence to new user
    connection.send(
      JSON.stringify({
        type: "presence",
        users: this.getPresence()
      })
    );

    // Notify others that someone joined
    this.broadcastPresence();
  }

  onClose(connection: Connection) {
    // No manual cleanup needed - connection state is automatically gone
    // Just broadcast updated presence to remaining users
    this.broadcastPresence();
  }

  // Heartbeat to update lastSeen
  onMessage(connection: Connection<UserState>, message: WSMessage) {
    if (message === "ping") {
      connection.setState((prev) => ({
        ...prev!,
        lastSeen: Date.now()
      }));
      connection.send("pong");
    }
  }

  // Build presence from all connections
  private getPresence() {
    const users: Record<string, { name: string; lastSeen: number }> = {};
    for (const conn of this.getConnections<UserState>()) {
      if (conn.state) {
        users[conn.id] = {
          name: conn.state.name,
          lastSeen: conn.state.lastSeen
        };
      }
    }
    return users;
  }

  private broadcastPresence() {
    this.broadcast(
      JSON.stringify({
        type: "presence",
        users: this.getPresence()
      })
    );
  }
}
```

## API Reference

### Agent Lifecycle Methods

<ParamField path="onStart" type="(props?) => void | Promise<void>">
  Called once when agent starts
</ParamField>

<ParamField path="onRequest" type="(request: Request) => Response | Promise<Response>">
  Handle HTTP requests
</ParamField>

<ParamField path="onConnect" type="(connection, ctx) => void | Promise<void>">
  WebSocket connected
</ParamField>

<ParamField path="onMessage" type="(connection, message) => void | Promise<void>">
  Message received
</ParamField>

<ParamField path="onClose" type="(connection, code, reason, wasClean) => void | Promise<void>">
  Connection closed
</ParamField>

<ParamField path="onError" type="(connection, error) => void | Promise<void>">
  WebSocket error
</ParamField>

<ParamField path="shouldSendProtocolMessages" type="(connection, ctx) => boolean">
  Control per-connection protocol frames (default: true)
</ParamField>

### Connection Management Methods

<ParamField path="getConnections" type="(tag?: string) => Iterable<Connection>">
  Get all connections, optionally by tag
</ParamField>

<ParamField path="getConnection" type="(id: string) => Connection | undefined">
  Get connection by ID
</ParamField>

<ParamField path="getConnectionTags" type="(connection, ctx) => string[]">
  Override to tag connections
</ParamField>

<ParamField path="broadcast" type="(message, without?: string[]) => void">
  Send to all connections
</ParamField>

<ParamField path="isConnectionProtocolEnabled" type="(connection) => boolean">
  Check if connection receives protocol messages
</ParamField>
