# How to Implement Endpoint Hooks for Before-Connect and After-Handshake Events in iroh

> Implement iroh endpoint hooks to intercept connections before connect and after handshake. Learn how to create and register custom EndpointHooks in this guide.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-07-05

---

**Implement endpoint hooks in iroh by creating a type that implements the `EndpointHooks` trait and registering it via `Endpoint::builder().hooks(your_hook)`, allowing you to intercept connections before they are established and immediately after the TLS handshake completes.**

The **iroh** networking stack from n0-computer provides a robust hook system for controlling connection lifecycles. By implementing endpoint hooks, you can execute custom logic at two critical moments: immediately before an outgoing connection attempt begins, and right after any connection's TLS handshake succeeds. This guide explains how to implement endpoint hooks for before-connect and after-handshake events in iroh based on the actual source implementation.

## The EndpointHooks Trait Interface

The hook system is defined in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs). Any type implementing the `EndpointHooks` trait can intercept connections through two asynchronous callbacks:

### Before-Connect Callback

The `before_connect` method runs synchronously when `Endpoint::connect` or `Endpoint::connect_with_opts` is invoked, but before any packets leave the node. It receives the remote address and ALPN protocol identifier, returning a `BeforeConnectOutcome` that either accepts or rejects the attempt.

```rust
async fn before_connect<'a>(
    &'a self,
    remote_addr: &'a EndpointAddr,
    alpn: &'a [u8],
) -> BeforeConnectOutcome;

```

Use this for pre-authentication checks, rate-limiting, or blocking specific endpoint IDs.

### After-Handshake Callback

The `after_handshake` method executes after the TLS handshake completes for both incoming and outgoing connections. At this point, the remote's endpoint ID and ALPN are fully validated. It returns an `AfterHandshakeOutcome` allowing you to accept the connection or reject it with a specific QUIC error code and reason string.

```rust
async fn after_handshake<'a>(&'a self, conn: &'a Connection) -> AfterHandshakeOutcome;

```

Use this for logging connection metrics, enforcing post-handshake authorization, or inspecting path statistics.

## Registering and Dispatching Hooks

Hooks are registered during endpoint construction through `Builder::hooks(hook)` in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs). The builder stores hooks in an internal `EndpointHooksList`, which supports multiple hooks called in registration order.

```rust
let endpoint = iroh::Endpoint::builder(presets::N0)
    .hooks(my_hook)          // Install the hook
    .bind()
    .await?;

```

According to the source code analysis, the dispatcher invokes these hooks at specific points:
- `before_connect` is called at lines 1096 and 1195 of [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) when initiating outbound connections
- `after_handshake` is called at line 2597 and within the `accept` flow after the TLS handshake finishes

If any hook returns `Reject`, the connection attempt aborts immediately without further network activity.

## Implementing a Before-Connect Hook

The following example demonstrates a hook that blocks connections to specific endpoint IDs before any network packets are transmitted:

```rust
use iroh::{
    endpoint::{EndpointHooks, BeforeConnectOutcome},
    EndpointAddr,
};

#[derive(Debug)]
struct BlocklistHook {
    blocked: iroh::EndpointId,
}

impl EndpointHooks for BlocklistHook {
    async fn before_connect<'a>(
        &'a self,
        remote_addr: &'a EndpointAddr,
        _alpn: &'a [u8],
    ) -> BeforeConnectOutcome {
        if remote_addr.id == self.blocked {
            BeforeConnectOutcome::Reject
        } else {
            BeforeConnectOutcome::Accept
        }
    }
}

```

Install the hook during endpoint setup:

```rust
let blocked = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef".parse().unwrap();
let hook = BlocklistHook { blocked };
let endpoint = iroh::Endpoint::builder(presets::N0)
    .hooks(hook)
    .bind()
    .await?;

```

## Implementing an After-Handshake Hook

The following hook logs connection details after the TLS handshake completes, using `Connection::remote_id` and `Connection::alpn` to inspect the established session:

```rust
use iroh::{
    endpoint::{EndpointHooks, AfterHandshakeOutcome, Connection},
};
use tracing::info;

#[derive(Debug)]
struct LoggingHook;

impl EndpointHooks for LoggingHook {
    async fn after_handshake<'a>(&'a self, conn: &'a Connection) -> AfterHandshakeOutcome {
        info!(
            remote = %conn.remote_id().fmt_short(),
            alpn = %std::str::from_utf8(conn.alpn()).unwrap_or("<invalid>"),
            "handshake completed"
        );
        AfterHandshakeOutcome::Accept
    }
}

```

To reject a connection with a custom error code, return `AfterHandshakeOutcome::Reject`:

```rust
AfterHandshakeOutcome::Reject {
    error_code: 403u32.into(),
    reason: b"not authenticated".to_vec(),
}

```

## Production Patterns from the iroh Repository

The iroh codebase includes two canonical examples demonstrating hook patterns:

**Remote Mapping ([`iroh/examples/remote-info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/remote-info.rs))**
Implements `RemoteMapHook` using only `after_handshake` to record connections and aggregate path statistics. The hook stores a weak handle to each connection via `Connection::weak_handle`, allowing safe monitoring without preventing connection closure.

**Authentication ([`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs))**
Demonstrates both callbacks working together:
- `OutgoingAuthHook` implements `before_connect` to verify pre-authentication status before dialing
- `IncomingAuthHook` implements `after_handshake` to reject unauthenticated peers with a 403 error code

## Critical Implementation Guidelines

When implementing `EndpointHooks`, observe these constraints from the source code:

- **Avoid Reference Cycles**: Do not store an `Endpoint` inside your hook. The hook is reference-counted by the endpoint, creating a cycle that leaks memory (documented in the "Notes to implementers" comment in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs)).
- **Use Weak Handles**: If you need to reference a connection after the hook returns, use `Connection::weak_handle` instead of holding the connection directly.
- **Minimize Latency**: Hooks run on the connection task. Offload heavy work to background tasks via channels to avoid blocking the endpoint.

## Summary

- Implement the `EndpointHooks` trait defined in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) to create custom hooks
- Use `before_connect` to intercept outgoing connections before packet transmission via `BeforeConnectOutcome::Accept` or `Reject`
- Use `after_handshake` to inspect established connections and optionally close them with custom QUIC error codes via `AfterHandshakeOutcome`
- Register hooks using `Endpoint::builder().hooks(hook)`; multiple hooks execute in registration order
- Consult [`iroh/examples/remote-info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/remote-info.rs) for monitoring patterns and [`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs) for authentication flows
- Never store `Endpoint` instances inside hooks to prevent memory leaks

## Frequently Asked Questions

### What is the difference between before_connect and after_handshake hooks?

The `before_connect` hook runs only for outgoing connections initiated via `Endpoint::connect`, allowing you to reject attempts before any network activity occurs. The `after_handshake` hook runs for both incoming and outgoing connections immediately after the TLS handshake succeeds, when the remote endpoint ID and ALPN are known but before application data flows.

### Can I register multiple hooks on a single endpoint?

Yes. The `Builder::hooks` method appends hooks to an internal `EndpointHooksList`. When events trigger, iroh iterates through all registered hooks in order, stopping if any hook returns a `Reject` outcome. This allows composable security policies where multiple checks must pass.

### How do I reject a connection with a custom error code?

In `after_handshake`, return `AfterHandshakeOutcome::Reject { error_code, reason }` where `error_code` is a QUIC application error code (converted via `.into()`) and `reason` is a byte slice containing a UTF-8 explanation. The remote peer receives this error when the connection closes.

### What happens if my hook performs expensive operations?

Hooks execute on the connection's async task. Blocking operations will delay connection establishment or acceptance. According to the implementation notes in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs), you should offload heavy computation to background tasks using channels or spawn tasks, keeping the hook callback lightweight and fast.