TL;DR:
- HTTP debugging proxies (Proxyman, mitmproxy, Charles) sit between your app and the internet, letting you see exactly what requests are being made and modify them in real time
- Proxyman is the best choice for macOS GUI work — fast setup, polished UI, good certificate handling; mitmproxy is better for scripting, automation, and non-macOS environments
- Both work with HTTPS through a man-in-the-middle CA certificate; install it once and all HTTPS traffic becomes inspectable
When an API integration isn’t working, you have two options: add logging to your code and redeploy, or just watch the actual HTTP traffic. An HTTP debugging proxy gives you the second option — a live, filterable view of every request your application makes, including the exact headers, body, and response, with the ability to modify or replay requests without touching your code.
This workflow is indispensable for debugging third-party API integrations, understanding what an SDK is actually doing, testing how your frontend handles various server responses, and reverse engineering undocumented APIs.
How HTTP Debugging Proxies Work
The tool configures itself as an HTTP/S proxy on your machine (typically 127.0.0.1:8080). Your application — browser, CLI tool, or mobile device on the same network — sends traffic through this proxy rather than directly to the destination. The proxy decrypts HTTPS traffic using a locally-generated CA certificate that you install as trusted on your system, inspects the content, then re-encrypts and forwards it.
This is technically a man-in-the-middle setup, which is why browsers and operating systems don’t do it automatically. You install the proxy’s CA certificate as trusted, and thereafter all HTTPS traffic from your machine is visible in the proxy. Certificate pinning in some apps will break this — apps that pin their TLS certificates refuse to communicate through a proxy.
Proxyman: The macOS Choice
Proxyman is a native macOS application with a genuinely good interface. Setup takes about two minutes: download, open, click “Install Certificate” in the Setup menu, trust it in Keychain, and traffic starts appearing.
What works well:
The filtering interface is fast. You can filter by domain, URL pattern, method, status code, or response content type in real time as requests stream in. The diff view for request comparison is useful for debugging inconsistent behaviour. The scripting tab lets you write JavaScript to modify requests or responses in flight — for example, replacing a production API base URL with a localhost one, or adding a debug header to every request.
Proxyman includes a “Map Remote” feature that redirects traffic from one URL to another without code changes. This is useful for testing against a staging server when your code hardcodes a production URL, or for pointing a third-party SDK at a mock server.
For iOS debugging, Proxyman handles the certificate installation workflow for iOS simulators automatically and provides a QR code for installing the certificate on a physical device over WiFi.
Breakpoints:
Set a breakpoint on any URL pattern, and Proxyman pauses traffic at that point, presenting you with an editable view of the request or response. Edit the headers, body, or status code, then click Resume. This is faster than mocking for one-off testing — you can test how your frontend handles a 429 Too Many Requests response by letting the real response arrive, pausing it, changing the status to 429 and the body to a rate-limit error, and resuming.
Scripting:
// Proxyman script: add auth header to all requests to api.example.com
async function onRequest(context, url, request) {
if (url.includes("api.example.com")) {
request.headers["X-Debug-Token"] = "dev-token-123";
}
return request;
}
Scripts run on every matching request and response. Common uses: injecting test tokens, logging request bodies to a file, replacing URLs, or normalising inconsistent response formats from a flaky API.
mitmproxy: Terminal-First and Scriptable
mitmproxy runs in the terminal and comes in three interfaces: mitmproxy (interactive TUI), mitmdump (streaming output, like tcpdump for HTTP), and mitmweb (browser-based UI). It’s the choice when you need scripting, are not on macOS, are running in CI, or need to intercept traffic from a containerised application.
Install: pip install mitmproxy or brew install mitmproxy.
Basic usage:
# Start mitmproxy on default port 8080
mitmproxy
# Dump all traffic to stdout (useful for piping to grep)
mitmdump
# Filter to a specific domain
mitmdump --flow-filter "~d api.example.com"
# Start with a script
mitmproxy -s my_script.py
Set your system proxy to 127.0.0.1:8080 and visit http://mitm.it to download and install the CA certificate.
Python scripting:
mitmproxy’s Python addon API is where it becomes genuinely powerful. Addons are Python classes with event hooks:
# addon.py — log all POST bodies to a file
import json
class LogPostBodies:
def request(self, flow):
if flow.request.method == "POST":
with open("post_log.jsonl", "a") as f:
f.write(json.dumps({
"url": flow.request.url,
"body": flow.request.text,
"headers": dict(flow.request.headers)
}) + "\n")
addons = [LogPostBodies()]
mitmproxy -s addon.py
Useful addon patterns:
- Request rewriting: change API base URLs, inject headers, modify JSON bodies
- Response mocking: return a fixed response for specific URL patterns (faster than running a mock server)
- Traffic capture: save flows to HAR files for later analysis
- Rate limiting simulation: add
time.sleep()in the response hook to simulate slow APIs - Error injection: randomly return 500 responses to test error handling
Using with Docker containers:
For debugging traffic from containerised applications, the easiest approach is to run mitmproxy in transparent proxy mode. Add this to your docker-compose.yml:
services:
myapp:
environment:
HTTP_PROXY: "http://host.docker.internal:8080"
HTTPS_PROXY: "http://host.docker.internal:8080"
NODE_TLS_REJECT_UNAUTHORIZED: "0" # For Node.js; use proper CA for production
For proper CA trust in containers, copy the mitmproxy CA cert into your container and add it to the system trust store.
Practical Workflows
Debugging a broken SDK integration: Run your SDK call, find the request in the proxy, and examine the exact headers and body being sent. Compare against the API documentation or a working curl call. Common issues: missing or malformed authentication headers, incorrect content-type, URL encoding problems, unexpected body format.
Understanding an undocumented API: Use the app that calls the API on your phone or machine while the proxy is running. Browse through the app’s UI to trigger API calls. Proxyman and mitmproxy will capture every request including the authentication mechanism, allowing you to replicate calls directly.
Testing error handling: Use breakpoints or mitmproxy addons to modify responses to return error conditions your application should handle — 401 Unauthorized, 429 Rate Limited, 503 Service Unavailable, malformed JSON, empty responses. Test this without mocking or modifying your backend.
Comparing production vs staging: Run two requests to different environments and use Proxyman’s diff view or mitmproxy’s flow comparison to identify exact differences in request construction or response format.
Certificate Pinning
Some applications (particularly mobile apps and security-conscious clients) implement certificate pinning, refusing to connect through any proxy. Signs of pinning: the app shows a network error specifically when the proxy is active, but works normally without it. Workarounds for development contexts include using a jailbroken/rooted device (for mobile), using an older app version without pinning, or patching the binary with Frida to disable pinning checks. For your own applications in development, don’t implement pinning against localhost or test environments — it makes your own debugging harder.
Both Proxyman and mitmproxy are tools for your own development and testing environment. Using them to inspect traffic from apps you don’t own, on systems you don’t control, or to capture credentials in production environments is a different matter entirely and is outside this guide’s scope.