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:
AuthorizationandProxy-AuthorizationCookieandSet-Cookie- Anything containing
api-key:X-Api-Key, Azure'sapi-key,X-Goog-Api-Key - Anything containing
subscription-key, such asOcp-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.