Skip to main content

Overview

Event hooks are methods that addons implement to respond to specific events in the proxy lifecycle. Mitmproxy automatically calls these methods when the corresponding event occurs.
All hooks are defined as dataclass-based Hook subclasses in the mitmproxy codebase. The hook name is automatically derived from the class name.

Lifecycle Hooks

These hooks manage the addon and proxy lifecycle.

load

Called when an addon is first loaded. Use this to register options and commands.
from mitmproxy.addonmanager import Loader

def load(self, loader: Loader) -> None:
    loader.add_option(
        name="my_option",
        typespec=bool,
        default=False,
        help="Enable my feature"
    )
Parameters:
  • loader: Loader - Provides methods to add options and commands
Source: mitmproxy/addonmanager.py:120

configure

Called when configuration changes. Fires during startup with all options in the updated set.
def configure(self, updated: set[str]) -> None:
    if "my_option" in updated:
        # React to option changes
        pass
Parameters:
  • updated: set[str] - Set of option names that changed
Source: mitmproxy/hooks.py:58

running

Called when the proxy is completely up and running. All addons are loaded and options are set.
def running(self) -> None:
    logging.info("Proxy is ready!")
Source: mitmproxy/hooks.py:81

done

Called when the addon shuts down. This is the final event an addon sees.
def done(self) -> None:
    # Cleanup resources
    self.close_connections()
Log handlers are shut down at this point, so calls to logging functions produce no output.
Source: mitmproxy/hooks.py:69

update

Called when flow objects have been modified, usually from a different addon.
def update(self, flows: Sequence[mitmproxy.flow.Flow]) -> None:
    for flow in flows:
        # React to flow updates
        pass
Parameters:
  • flows: Sequence[Flow] - List of modified flow objects
Source: mitmproxy/hooks.py:89

HTTP Hooks

These hooks handle HTTP/HTTPS traffic.

requestheaders

HTTP request headers were successfully read. The body is empty at this point.
from mitmproxy import http

def requestheaders(self, flow: http.HTTPFlow) -> None:
    # Modify headers before body is received
    flow.request.headers["X-Custom"] = "value"
Parameters:
  • flow: http.HTTPFlow - The HTTP flow (body is empty)
Use cases:
  • Early header inspection
  • Setting streaming mode
  • Blocking requests early
Source: mitmproxy/proxy/layers/http/_hooks.py:8

request

The full HTTP request has been read.
def request(self, flow: http.HTTPFlow) -> None:
    # Access complete request including body
    if b"password" in flow.request.content:
        flow.request.content = b"REDACTED"
Parameters:
  • flow: http.HTTPFlow - Complete HTTP request
If request streaming is active, this fires after the entire body has been streamed. HTTP trailers can still be modified.
Source: mitmproxy/proxy/layers/http/_hooks.py:18

responseheaders

HTTP response headers were successfully read. The body is empty at this point.
def responseheaders(self, flow: http.HTTPFlow) -> None:
    # Enable streaming for large responses
    if int(flow.response.headers.get("content-length", 0)) > 1000000:
        flow.response.stream = True
Parameters:
  • flow: http.HTTPFlow - The HTTP flow (response body is empty)
Source: mitmproxy/proxy/layers/http/_hooks.py:33

response

The full HTTP response has been read.
def response(self, flow: http.HTTPFlow) -> None:
    # Modify response
    flow.response.status_code = 200
    flow.response.text = "Modified content"
Parameters:
  • flow: http.HTTPFlow - Complete HTTP response
Source: mitmproxy/proxy/layers/http/_hooks.py:42

error

An HTTP error has occurred (e.g., invalid server responses, interrupted connections).
def error(self, flow: http.HTTPFlow) -> None:
    logging.error(f"HTTP error: {flow.error.msg}")
Parameters:
  • flow: http.HTTPFlow - Flow with error set
Every flow receives either error or response, but not both.
Source: mitmproxy/proxy/layers/http/_hooks.py:56

HTTP CONNECT Hooks

These hooks handle HTTP CONNECT requests for proxy tunneling.

http_connect

An HTTP CONNECT request was received.
def http_connect(self, flow: http.HTTPFlow) -> None:
    # Block certain CONNECT requests
    if flow.request.host == "blocked.com":
        flow.response = http.Response.make(
            403, b"Forbidden"
        )
Parameters:
  • flow: http.HTTPFlow - The CONNECT request
Setting a non-2xx response returns it to the client and aborts the connection.
Source: mitmproxy/proxy/layers/http/_hooks.py:70

http_connect_upstream

An HTTP CONNECT request is about to be sent to an upstream proxy.
def http_connect_upstream(self, flow: http.HTTPFlow) -> None:
    # Set custom authentication for upstream proxy
    flow.request.headers["Proxy-Authorization"] = "Basic ..."
Source: mitmproxy/proxy/layers/http/_hooks.py:87

Connection Hooks

These hooks handle client and server connections.

client_connected

A client has connected to mitmproxy.
from mitmproxy import connection

def client_connected(self, client: connection.Client) -> None:
    logging.info(f"Client connected: {client.address}")
Parameters:
  • client: connection.Client - The client connection
Setting client.error kills the connection.
Source: mitmproxy/proxy/server_hooks.py:8

client_disconnected

A client connection has been closed.
def client_disconnected(self, client: connection.Client) -> None:
    logging.info(f"Client disconnected: {client.address}")
Parameters:
  • client: connection.Client - The closed client connection
Source: mitmproxy/proxy/server_hooks.py:20

server_connect

Mitmproxy is about to connect to a server.
def server_connect(self, data: ServerConnectionHookData) -> None:
    # Redirect to different server
    data.server.address = ("other.example.com", 443)
Parameters:
  • data.server: connection.Server - The server connection
  • data.client: connection.Client - The client on the other end
Source: mitmproxy/proxy/server_hooks.py:39

server_connected

Mitmproxy has connected to a server.
def server_connected(self, data: ServerConnectionHookData) -> None:
    logging.info(f"Connected to {data.server.address}")
Source: mitmproxy/proxy/server_hooks.py:51

server_disconnected

A server connection has been closed.
def server_disconnected(self, data: ServerConnectionHookData) -> None:
    pass
Source: mitmproxy/proxy/server_hooks.py:60

server_connect_error

Mitmproxy failed to connect to a server.
def server_connect_error(self, data: ServerConnectionHookData) -> None:
    logging.error(f"Failed to connect: {data.server.error}")
Every server connection receives either server_connected or server_connect_error, but not both.
Source: mitmproxy/proxy/server_hooks.py:69

WebSocket Hooks

These hooks handle WebSocket connections.

websocket_start

A WebSocket connection has commenced.
def websocket_start(self, flow: http.HTTPFlow) -> None:
    logging.info("WebSocket connection started")
Parameters:
  • flow: http.HTTPFlow - The HTTP flow that initiated WebSocket
Source: mitmproxy/proxy/layers/websocket.py:24

websocket_message

A WebSocket message was received.
def websocket_message(self, flow: http.HTTPFlow) -> None:
    # Get the latest message
    message = flow.websocket.messages[-1]
    
    # Modify message content
    if message.from_client:
        message.content = message.content.replace(b"foo", b"bar")
    
    # Drop message
    if b"DROP" in message.content:
        message.drop()
Parameters:
  • flow: http.HTTPFlow - Flow containing WebSocket messages
Message types:
  • Opcode.TEXT - Text message
  • Opcode.BINARY - Binary message
Source: mitmproxy/proxy/layers/websocket.py:33

websocket_end

A WebSocket connection has ended.
def websocket_end(self, flow: http.HTTPFlow) -> None:
    close_code = flow.websocket.close_code
    logging.info(f"WebSocket closed: {close_code}")
Parameters:
  • flow: http.HTTPFlow - The completed WebSocket flow
Source: mitmproxy/proxy/layers/websocket.py:45

TCP Hooks

These hooks handle raw TCP connections.

tcp_start

A TCP connection has started.
from mitmproxy import tcp

def tcp_start(self, flow: tcp.TCPFlow) -> None:
    logging.info(f"TCP connection to {flow.server_conn.address}")
Parameters:
  • flow: tcp.TCPFlow - The TCP flow
Source: mitmproxy/proxy/layers/tcp.py:17

tcp_message

A TCP connection has received a message.
def tcp_message(self, flow: tcp.TCPFlow) -> None:
    message = flow.messages[-1]
    # Modify TCP message
    message.content = message.content.replace(b"foo", b"bar")
Parameters:
  • flow: tcp.TCPFlow - Flow with the latest message in flow.messages[-1]
TCP is stream-based, not message-based. Message boundaries are arbitrary and depend on socket.recv() calls.
Source: mitmproxy/proxy/layers/tcp.py:26

tcp_end

A TCP connection has ended.
def tcp_end(self, flow: tcp.TCPFlow) -> None:
    logging.info(f"TCP connection ended: {len(flow.messages)} messages")
Source: mitmproxy/proxy/layers/tcp.py:36

tcp_error

A TCP error has occurred.
def tcp_error(self, flow: tcp.TCPFlow) -> None:
    logging.error(f"TCP error: {flow.error.msg}")
Every TCP flow receives either tcp_error or tcp_end, but not both.
Source: mitmproxy/proxy/layers/tcp.py:45

DNS Hooks

These hooks handle DNS queries and responses.

dns_request

A DNS query has been received.
import ipaddress
from mitmproxy import dns

def dns_request(self, flow: dns.DNSFlow) -> None:
    q = flow.request.question
    if q.name == "example.com" and q.type == dns.types.A:
        # Spoof DNS response
        flow.response = flow.request.succeed([
            dns.ResourceRecord(
                name="example.com",
                type=dns.types.A,
                class_=dns.classes.IN,
                ttl=300,
                data=ipaddress.ip_address("127.0.0.1").packed,
            )
        ])
Parameters:
  • flow: dns.DNSFlow - The DNS flow
Source: mitmproxy/proxy/layers/dns.py:20

dns_response

A DNS response has been received or set.
def dns_response(self, flow: dns.DNSFlow) -> None:
    # Log DNS response
    for answer in flow.response.answers:
        logging.info(f"DNS answer: {answer.name} -> {answer.data}")
Parameters:
  • flow: dns.DNSFlow - Flow with DNS response
Source: mitmproxy/proxy/layers/dns.py:29

dns_error

A DNS error has occurred.
def dns_error(self, flow: dns.DNSFlow) -> None:
    logging.error(f"DNS error: {flow.error.msg}")
Parameters:
  • flow: dns.DNSFlow - Flow with error
Source: mitmproxy/proxy/layers/dns.py:38

Hook Naming Convention

Hook names are automatically derived from their class names:
# Class: HttpRequestHook -> Hook name: http_request
# Class: DnsRequestHook -> Hook name: dns_request
# Class: TcpMessageHook -> Hook name: tcp_message
The conversion follows this pattern:
  1. Remove “Hook” suffix
  2. Insert underscores before capital letters
  3. Convert to lowercase
Source: mitmproxy/hooks.py:36

Deprecated Hooks

The following hooks have been removed or deprecated:
  • clientconnect → Use client_connected
  • clientdisconnect → Use client_disconnected
  • serverconnect → Use server_connect and server_connected
  • serverdisconnect → Use server_disconnected
  • add_log → Use Python’s built-in logging module

Event Ordering

Typical event sequence for an HTTP flow:
client_connected

requestheaders

request

server_connect (if needed)

server_connected

responseheaders

response

client_disconnected (eventually)
With streaming enabled, event order may change. For example, response may occur before request if the server replies with “413 Payload Too Large” during upload.

Next Steps

Examples

See real-world examples using these hooks

Built-in Addons

Learn from built-in addon implementations

Build docs developers (and LLMs) love