PHPackages                             nordkit/wiretap - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. nordkit/wiretap

ActiveLibrary[HTTP &amp; Networking](/categories/http)

nordkit/wiretap
===============

Tap into your app's HTTP traffic. Filter, redact, and store outbound requests with zero boilerplate.

v2.4.1(1mo ago)1480↓100%MITPHPPHP ^8.3CI passing

Since Apr 18Pushed 1mo agoCompare

[ Source](https://github.com/nordkit/wiretap)[ Packagist](https://packagist.org/packages/nordkit/wiretap)[ RSS](/packages/nordkit-wiretap/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (8)Versions (18)Used By (0)

Wiretap
=======

[](#wiretap)

Tap into your app's HTTP traffic. Filter, redact, and store inbound and outbound requests with zero boilerplate.

A highly configurable HTTP tracing package, built for Laravel. Wiretap automatically captures every outbound request and response — and optionally every inbound request too — including headers, payloads, status codes, and timing. Stores them securely with full control over what gets kept, what gets scrubbed, and where it all ends up. Works with any PHP application via a lightweight `TraceWriter` interface.

### Key Features

[](#key-features)

- **Storage Backends**: Persist traces to your SQL `database` (default) or stream them to a structured Laravel `log` channel.
- **Automatic Laravel Integration**: Zero-config capture of all Laravel HTTP Client requests via event listeners. Opt-in capture of all inbound requests via global middleware.
- **Native Guzzle Support**: Drop-in `WiretapClient` wrapper and `WiretapMiddleware` for existing Guzzle stacks.
- **Advanced Redaction**: Automatically scrubs sensitive headers and recursively redacts JSON payload keys before anything touches storage.
- **Filtering &amp; Truncation**: Allowlist/denylist hosts, exclude URL patterns, and cap body sizes to keep your storage lean.
- **Eloquent Polymorphism**: Attach traces to any Eloquent model with `withTraceable()` / `->traceable()` and `HasTraces` — then query them back in one line.
- **Manual Tracing**: Use `Wiretap::trace()` to capture requests from raw cURL, custom SDKs, or any HTTP client — with built-in timing, caller attribution (`callerClass` / `callerMethod`), and safe exception handling.

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 11.0 / 12.0 / 13.0

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require nordkit/wiretap
```

### Laravel Projects

[](#laravel-projects)

You can publish and run the migrations with:

```
php artisan vendor:publish --tag="wiretap-migrations"
php artisan migrate
```

You can publish the config file with:

```
php artisan vendor:publish --tag="wiretap-config"
```

> **Upgrading from an earlier version?** Migrations are auto-loaded by the service provider, so running `php artisan migrate` is all that is needed to pick up new columns. If you previously published the migration files into your own `database/migrations` directory, re-publish with `--force` to get the latest files before migrating:
>
> ```
> php artisan vendor:publish --tag="wiretap-migrations" --force
> php artisan migrate
> ```

### Non-Laravel Projects

[](#non-laravel-projects)

If you are using this package in a standalone PHP application (without the Laravel framework), you will need to manually handle the database schema or inject a custom `TraceWriter` into the `Wiretap`.

If you choose to use the built-in database writer (which depends on `illuminate/database`), you must manually run this equivalent raw SQL to create the `wiretap_traces` table:

```
CREATE TABLE `wiretap_traces` (
  `id` CHAR(26) NOT NULL,
  `direction` VARCHAR(10) NOT NULL,
  `driver` VARCHAR(20) NOT NULL,
  `url` TEXT NOT NULL,
  `method` VARCHAR(10) NOT NULL,
  `request_headers` JSON NULL,
  `request_body` LONGTEXT NULL,
  `response_status` INT NULL,
  `response_headers` JSON NULL,
  `response_body` LONGTEXT NULL,
  `duration_ms` INT UNSIGNED NOT NULL,
  `error_message` TEXT NULL,
  `ip_address` VARCHAR(45) NULL,
  `caller_class` VARCHAR(255) NULL,
  `caller_method` VARCHAR(255) NULL,
  `traceable_type` VARCHAR(255) NULL,
  `traceable_id` CHAR(26) NULL,
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  INDEX `wiretap_traces_traceable_type_traceable_id_created_at_index` (`traceable_type`, `traceable_id`, `created_at`)
);
```

To entirely bypass `illuminate/database`, you can implement the `TraceWriter` interface and store your traces using raw PDO, Monolog, or any other solution:

```
use Nordkit\Wiretap\Contracts\TraceWriter;
use Nordkit\Wiretap\HttpExchange;

class MyPdoWriter implements TraceWriter
{
    public function write(HttpExchange $exchange): void
    {
        // Insert $exchange data using raw PDO, e.g.:
        // $exchange->ipAddress    — caller IP (null unless explicitly passed)
        // $exchange->callerClass  — originating class name (null unless explicitly passed)
        // $exchange->callerMethod — originating method name (null unless explicitly passed)
    }
}
```

#### Inbound tracing and IP capture

[](#inbound-tracing-and-ip-capture)

There is no equivalent of `WiretapInboundMiddleware` outside Laravel — you wire inbound tracing yourself inside your framework's request pipeline. Use `Wiretap::trace()` and pass the caller's IP via the `ipAddress` parameter:

```
use Nordkit\Wiretap\HttpDirection;

$timer = $wiretap->start();

// ... dispatch the request through your application ...

$wiretap->trace(
    direction: HttpDirection::Inbound,
    driver: 'my-framework',
    url: 'https://api.myapp.com' . $_SERVER['REQUEST_URI'],
    method: $_SERVER['REQUEST_METHOD'],
    requestHeaders: getallheaders(),
    requestBody: file_get_contents('php://input') ?: null,
    responseStatus: http_response_code(),
    responseHeaders: [],
    responseBody: null,
    timer: $timer,
    ipAddress: $_SERVER['REMOTE_ADDR'] ?? null,
);
```

Or construct an `HttpExchange` directly and call `capture()` when you need full control:

```
use Nordkit\Wiretap\HttpExchange;
use Nordkit\Wiretap\HttpDirection;

$wiretap->capture(new HttpExchange(
    direction: HttpDirection::Inbound,
    driver: 'my-framework',
    url: $request->getUri(),
    method: $request->getMethod(),
    requestHeaders: $request->getHeaders(),
    requestBody: $request->getBody(),
    responseStatus: $response->getStatusCode(),
    responseHeaders: $response->getHeaders(),
    responseBody: $response->getBody(),
    durationMs: $durationMs,
    ipAddress: $request->getServerParam('REMOTE_ADDR'),
));
```

> **Privacy note:** `ip_address` is `null` by default everywhere. Only populate it when you have a legitimate need and appropriate data-retention policies in place — IP addresses are personal data under GDPR and similar regulations.

Usage
-----

[](#usage)

### Configuration

[](#configuration)

Publish the config file to get started:

```
php artisan vendor:publish --tag="wiretap-config"
```

Every option in `config/wiretap.php` is documented with an inline comment. The main areas to know about:

- **`enabled` / `debug`** — kill switch and error visibility. By default, all tracing exceptions are swallowed silently; set `debug = true` to forward them to Laravel's `report()` handler.
- **`driver`** — `database` (default, queued via `WriteTraceJob`) or `log` (streams to a Laravel log channel).
- **`outbound.*`** — controls the Laravel HTTP Client and Guzzle adapters, plus host/path filtering for outbound requests.
- **`inbound.*`** — opt-in capture of incoming requests. Disabled by default (`WIRETAP_INBOUND=false`). Includes the same host/path filtering as outbound.
- **`store_request_body` / `store_response_body`** — toggle body capture. Binary content types (`image/*`, `video/*`, `audio/*`, `multipart/form-data`, `application/octet-stream`, `application/pdf`, `application/zip`) are never stored as raw bytes — instead a `[binary: filename.ext]` placeholder is stored, with the filename extracted from `Content-Disposition` where available.
- **`max_body_bytes`** — caps stored body size to 64 KB by default. Set to `null` for unlimited.
- **`redact_request_headers` / `redact_response_headers` / `redact_body_keys`** — lists of headers and JSON keys to scrub before anything reaches storage.
- **`pruning.*`** — automatic deletion of old traces via `php artisan wiretap:prune`. Disabled by default, only applies to the `database` driver.

> **Caller attribution:** The `caller_class` and `caller_method` columns are always `null` for automatic Laravel HTTP Client and Guzzle traces. They are only populated when you pass `callerClass` / `callerMethod` to `Wiretap::trace()` manually.

### Pruning old traces

[](#pruning-old-traces)

Traces accumulate quickly. Enable automatic pruning by setting:

```
WIRETAP_PRUNING_ENABLED=true
WIRETAP_PRUNING_KEEP_DAYS=90   # default: 90 days
```

When `WIRETAP_PRUNING_ENABLED=true` and `wiretap.driver` is `database`, Wiretap automatically registers a daily schedule for `wiretap:prune` — no entry in your scheduler is required.

You can also run it on demand or with a custom retention window:

```
php artisan wiretap:prune
php artisan wiretap:prune --days=30
```

> **Note:** Pruning only applies to the `database` driver. Running `wiretap:prune` when `wiretap.driver` is `log` will print a warning and exit cleanly.

### Laravel Integration

[](#laravel-integration)

#### Laravel HTTP Client

[](#laravel-http-client)

The package automatically integrates with the Laravel HTTP Client. You don't need any additional setup.

```
use Illuminate\Support\Facades\Http;

$response = Http::withHeaders(['X-First' => 'foo'])
    ->get('https://api.github.com/users/octocat');

// The request and response are now automatically stored in the database.
```

#### Inbound HTTP Traffic

[](#inbound-http-traffic)

Wiretap can also capture all **incoming** requests to your application. This is disabled by default — opt-in by setting the environment variable:

```
WIRETAP_INBOUND=true
```

Or enable it directly in the config:

```
'inbound' => [
    'laravel_http' => true,
],
```

When enabled, `WiretapInboundMiddleware` is automatically pushed onto the global HTTP kernel. Every request your app receives — and its response — will be traced through the same pipeline as outbound traffic, including redaction and filtering.

You can limit which inbound requests are traced using the `include_paths` and `exclude_paths` options. For example, to only trace webhook callbacks:

```
'inbound' => [
    'laravel_http' => true,
    'include_paths' => ['#^/webhooks#'],
    'exclude_paths' => ['#^/health#'],
],
```

Or restrict tracing to a specific subdomain in a multi-domain app:

```
'inbound' => [
    'laravel_http' => true,
    'include_hosts' => ['webhooks.myapp.com'],
],
```

> **Note:** `inbound.include_hosts` / `exclude_hosts` are matched against the `Host` header of the incoming request — i.e. your own app's domain. They are not matched against the remote caller's IP or hostname.

To capture the caller's IP address, enable the opt-in flag:

```
WIRETAP_INBOUND_STORE_IP=true
```

Or in the config:

```
'inbound' => [
    'store_ip' => true,
],
```

The value is stored in the `ip_address` column (varchar 45, covers IPv4 and IPv6) and is populated via `$request->ip()`, which respects your app's `TrustProxies` configuration.

> **Privacy note:** IP addresses are personal data under GDPR and similar regulations. This option is disabled by default — only enable it when you have a legitimate need and appropriate data-retention policies in place.

#### Polymorphic Relations

[](#polymorphic-relations)

You can associate HTTP traces with your Eloquent models using polymorphic relations. This is useful for tracking which requests belong to a specific record, such as a user, order, or shipment.

When using the Laravel HTTP Client, you can use the built-in macro to attach a model to the request:

```
use Illuminate\Support\Facades\Http;

$order = Order::find(1);

Http::withTraceable($order)
    ->post('https://api.example.com/orders/sync', $order->toArray());
```

> **Note:** `withTraceable()` stores the model in a per-process singleton and is designed for sequential requests. When using `Http::pool()` with multiple concurrent requests, only the most recently set traceable will be attached. For concurrent use-cases, construct an `HttpExchange` directly with the desired `traceable` and call `Wiretap::capture()` manually.

For **inbound** requests, attach a model to the trace using the `->traceable()` route macro. It scans the route's already-resolved model bindings and pushes the first match onto the trace scope:

```
use App\Models\Order;

Route::post('/orders/{order}/sync', OrderSyncController::class)
    ->traceable(Order::class);
```

> **Note:** `->traceable()` relies on route model binding being resolved before it runs. Routes registered in the `web` or `api` middleware group satisfy this automatically via `SubstituteBindings`.

To easily retrieve the associated traces, add the provided trait (or manually define the `morphMany` relationship) on your Eloquent model:

```
use Illuminate\Database\Eloquent\Model;
use Nordkit\Wiretap\Laravel\Concerns\HasTraces;

class Order extends Model
{
    use HasTraces;
}
```

Now you can access the previous HTTP traces directly from your model instance:

```
$traces = $order->traces;
```

### Guzzle HTTP Client

[](#guzzle-http-client)

#### Automatic Logging (Recommended)

[](#automatic-logging-recommended)

The package provides a `WiretapClient` wrapper that handles all logging automatically. It is registered as a singleton in the Laravel service container and can be injected directly via the constructor:

```
use Nordkit\Wiretap\Guzzle\WiretapClient;

class GitHubService
{
    public function __construct(private readonly WiretapClient $http) {}

    public function getUser(string $username): array
    {
        $response = $this->http->get("https://api.github.com/users/{$username}");

        // The request and response are automatically logged.
        return json_decode((string) $response->getBody(), true);
    }
}
```

You can associate requests with an Eloquent model using `withTraceable()`, mirroring the Laravel HTTP Client behavior:

```
$response = $this->http
    ->withTraceable($order)
    ->post('https://api.example.com/orders/sync', ['json' => $order->toArray()]);
```

> **Note:** `withTraceable()` is designed for sequential requests. The traceable is consumed (reset to `null`) after the next request is dispatched.

To pass custom Guzzle config options (e.g. `base_uri`, `timeout`), resolve the client manually with `WiretapClient::make()`:

```
use Nordkit\Wiretap\Guzzle\WiretapClient;
use Nordkit\Wiretap\Wiretap;

$client = WiretapClient::make(app(\Nordkit\Wiretap\Wiretap::class), [
    'base_uri' => 'https://api.example.com',
    'timeout'  => 10,
]);
```

#### Manual Middleware

[](#manual-middleware)

If you need to attach logging to an existing Guzzle `HandlerStack` (e.g. wrapping a third-party SDK), push the middleware directly:

```
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Nordkit\Wiretap\Guzzle\WiretapMiddleware;
use Nordkit\Wiretap\Wiretap;

$stack = HandlerStack::create();
$stack->push(WiretapMiddleware::make(app(\Nordkit\Wiretap\Wiretap::class)));

$client = new Client(['handler' => $stack]);

$client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
```

### Manual Logging

[](#manual-logging)

If you need to trace requests made outside of Laravel's HTTP Client or Guzzle (for example, raw cURL requests or third-party SDKs), you can easily record entries using the built-in timer and the `Wiretap::trace()` helper method.

The `trace()` method safely swallows all exceptions, guaranteeing that tracing will never halt your application execution.

```
use Nordkit\Wiretap\HttpDirection;
use Nordkit\Wiretap\Laravel\Facades\Wiretap;

// 1. Start the internal timer — returns a Closure that yields elapsed ms when called
$timer = Wiretap::start();

// 2. Perform your manual request/interaction...
$response = $customSdk->syncData(['foo' => 'bar']);

// 3. Trace the execution using the timer Closure
Wiretap::trace(
    direction: HttpDirection::Outbound,
    driver: 'custom-sdk',
    url: 'https://api.example.com/sync',
    method: 'POST',
    requestHeaders: ['Content-Type' => 'application/json'],
    requestBody: json_encode(['data' => 'sync-me']),
    responseStatus: 200,
    responseHeaders: ['Content-Type' => 'application/json'],
    responseBody: json_encode(['status' => 'success']),
    timer: $timer, // Automagically resolves the request duration
    errorMessage: null, // Populate if your manual implementation encountered an error
    callerClass: self::class,   // Optional: record which class initiated the call
    callerMethod: __FUNCTION__, // Optional: record which method initiated the call
);
```

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Releasing
---------

[](#releasing)

Please see [RELEASING](RELEASING.md) for instructions on how to cut a new release.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](https://github.com/nordkit/wiretap/security/policy) on how to report security vulnerabilities.

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance90

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity57

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 83.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

17

Last Release

50d ago

Major Versions

v1.2.3 → v2.0.02026-04-19

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/5223785?v=4)[Mattias Jarestad](/maintainers/mjarestad)[@mjarestad](https://github.com/mjarestad)

---

Top Contributors

[![mjarestad](https://avatars.githubusercontent.com/u/5223785?v=4)](https://github.com/mjarestad "mjarestad (50 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (10 commits)")

---

Tags

httplaravelmonitoringGuzzletracingwiretap

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/nordkit-wiretap/health.svg)

```
[![Health](https://phpackages.com/badges/nordkit-wiretap/health.svg)](https://phpackages.com/packages/nordkit-wiretap)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)[rapidez/core

Rapidez Core

1822.4k65](/packages/rapidez-core)[dragon-code/laravel-http-logger

Logging incoming HTTP requests

319.8k3](/packages/dragon-code-laravel-http-logger)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
