Requizon

Recording headers

Which headers are kept in each direction, how sensitive ones are masked, and how to narrow the list.

When an integration goes wrong, the answer is often in a header rather than in the body. Retry-After and the X-RateLimit-* family say when you may call again and how much of your budget is left. The provider's X-Request-Id or CF-Ray is what their support desk asks for. Cache-Control and ETag explain a response you thought you had already fetched. Requizon stores headers beside the parameters and the response body, and shows them in the request row's Details panel.

Choosing which messages are captured#

Capture is configured once, for every API, under recording.headers:

// config/requizon.php
'recording' => [
    // ...
    'headers' => [
        'request' => env('REQUIZON_REQUEST_HEADERS', 'none'),       // none | failures | all
        'response' => env('REQUIZON_RESPONSE_HEADERS', 'failures'), // none | failures | all
        'only' => [],              // e.g. ['x-ratelimit-*', 'retry-after', 'x-request-id']
        'except' => [],
        'redact' => ['authorization', 'proxy-authorization', 'cookie', 'set-cookie', '*api-key*', '*subscription-key*', 'x-functions-key'],
        'max_bytes' => 16 * 1024,
    ],
],

The two directions are set independently and take the same three values. none stores nothing, all stores every header of every recorded call, and failures stores them only for calls recorded as a failure, which means the same thing in either direction: the same rows that keep a response body. Out of the box the request's headers are not stored at all and the response's are stored for failures, so a fresh install gives you the headers of the calls you are most likely to open and nothing for the rest. REQUIZON_REQUEST_HEADERS and REQUIZON_RESPONSE_HEADERS set the two from the environment.

What the Details panel shows#

Expanding a request row prints one line per value, in the shape they had on the wire, so a response that sets two cookies gets two Set-Cookie lines:

Date: Fri, 18 Sep 2026 09:14:02 GMT
Content-Type: application/json
X-Request-Id: req_8fJk2Lm0Qd
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1758186900
Retry-After: 42
Set-Cookie: ***
Set-Cookie: ***

Request headers appear above the Query and Body blocks, response headers below them and above the response body, so the panel reads in the order the call happened.

Masking sensitive headers#

A header whose name is sensitive keeps its name and its number of values, and every value becomes ***. That way you can still see that the header was sent, and how many times, without the credential reaching the table. Names are matched case-insensitively, so X-Api-Key and x-api-key are the same header.

Masked by default:

  • Authorization and Proxy-Authorization
  • Cookie and Set-Cookie
  • Anything containing api-key: X-Api-Key, Azure's api-key, X-Goog-Api-Key
  • Anything containing subscription-key, such as Ocp-Apim-Subscription-Key
  • X-Functions-Key

On top of that list, a header is masked when its name matches the parameter rules in force for its API: the global redact_patterns and redact_exact, plus whatever that API's apis entry adds. See Redaction for both. One provider's X-Partner-Token is caught by the global token pattern without you naming it here, and a header only that provider sends can be named on its entry and masked nowhere else.

redact takes Str::is() patterns, and the list you write replaces the default one, so copy across the entries you still want. A published config that has no redact key at all keeps the defaults, which is what an application that published its config before header capture existed will have.

Keeping a shortlist with only and except#

only and except also take Str::is() patterns matched against the lowercased name. only keeps just the headers it matches; except then removes some of what is left. A header that only lets through is still subject to redaction, so a shortlist cannot accidentally expose an Authorization header.

'headers' => [
    'response' => 'all',
    'only' => ['x-ratelimit-*', 'retry-after', 'x-request-id', 'cf-ray', 'location'],
    // ...
],

A shortlist is what makes all affordable. Every recorded call then stores five short headers instead of the twenty a CDN adds to each response.

The size cap#

max_bytes (16 KB) caps the stored header JSON of each message. JSON cannot be cut part of the way through, so a header that does not fit is dropped whole rather than truncated, and the dropped ones are counted under _truncated:

{"Date": ["Fri, 18 Sep 2026 09:14:02 GMT"], "_truncated": ["2 headers omitted"]}

Headers are considered in the order they arrived, and one that does not fit is skipped rather than ending the list, so a single enormous Content-Security-Policy does not cost you every header after it.

Following a redirect#

A redirect is recorded hop by hop, but a 3xx is not a failure, so failures never stores the hops. To read each hop's Location, set response to all, preferably with an only list so you are not storing every header of every successful call as well.

What capture costs#

Headers are already parsed by the time Requizon sees them, so capturing them reads nothing extra off the wire. The cost is table size, which is why the default keeps the response headers of failures alone. If you turn all on for both directions, keep retention.detail_days short and expect the detail table to grow noticeably faster than it did.

Common questions

Does Requizon store request and response headers?

By default it keeps the response headers of calls recorded as a failure, and no request headers at all. Both directions are set independently to none, failures or all, under recording.headers in config/requizon.php or through REQUIZON_REQUEST_HEADERS and REQUIZON_RESPONSE_HEADERS.

Are Authorization headers stored by Requizon?

The name is stored, the value is not. Authorization, Proxy-Authorization, Cookie, Set-Cookie, X-Functions-Key, anything containing api-key or subscription-key, and any header matching the parameter redaction rules in force for that API are kept with every value replaced by ***, so you can still see the header was sent. Names match case-insensitively.

How do I record only rate-limit headers?

Set recording.headers.only to the patterns you want, such as x-ratelimit-*, retry-after and x-request-id. They are Str::is() patterns matched against the lowercased name, except removes some of what only let through, and a header only keeps is still subject to redaction.

Install Requizon today.

Checkout ends with your license key, and the installation guide takes it from there.