onRequest() and onResponse() fire on every attempt, retries included, with a payload that carries no API key. Hooks run in the order they were registered:

use Fkrzski\SteamApiSdk\Hooks\RequestSending;
use Fkrzski\SteamApiSdk\Hooks\ResponseReceived;
$connector
->onRequest(fn (RequestSending $request) => $logger->debug($request->method, $request->query))
->onResponse(fn (ResponseReceived $response) => $logger->info(sprintf(
'%s answered %d in %.3fs on attempt %d',
$response->method,
$response->status,
$response->duration,
$response->attempt,
)));
Property Type Value
method string ISteamUser/GetPlayerSummaries/v2
query array<string, mixed> the query Steam receives, without key
attempt int 1, then 2 for the first retry; always 1 under sendAsync() and pool()
status int ResponseReceived only: the HTTP status
duration float ResponseReceived only: seconds from sending to the answer, onRequest hooks excluded

A 429 reaches onResponse before the rate limiter turns it into SteamRateLimitException. A request refused locally, for the quota or a missing key, fires neither hook, and an attempt Steam never answered fires onRequest alone. middleware() still covers headers, the body and anything else lower-level, key included.

onFailure() fires once per call with the exception the caller is about to get: after the retries are spent, and as the SDK exception rather than Saloon’s.

$connector->onFailure(fn (Throwable $e) => $logger->error($e->getMessage(), ['exception' => $e]));

It fires for a request refused locally, for the quota or a missing key, which neither hook above sees, and for an exception thrown by another hook or a middleware. Under sendAsync() and pool() it fires once for every request that fails, before its promise rejects, and so it does for every chunk that fails in a list over 100 IDs.

debug(), debugRequest() and debugResponse() are Saloon’s. They register on the connector, so every call after them is dumped. A dump pasted into an issue or a log is where a key leaks, so the API key comes out masked as key=***:

$connector->debug()->users()->summaries([$id]);
php steam.php
Saloon Request (GetPlayerSummariesRequest) -> array:6 [
"connector" => "Fkrzski\SteamApiSdk\SteamConnector"
"request" => "Fkrzski\SteamApiSdk\Http\Requests\ISteamUser\GetPlayerSummariesRequest"
"method" => "GET"
"uri" => "https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key=***&steamids=76561197960435530"
"headers" => array:1 [
"Host" => "api.steampowered.com"
]
"body" => ""
]

The default dumper is symfony/var-dumper, which the SDK suggests rather than requires:

Terminal window
composer require --dev symfony/var-dumper

A callable passed to debugRequest() gets the same masked PSR request, and only that request is masked. The PendingRequest beside it, the Response in debugResponse(), and debugRequest() called on a request object rather than the connector all still reach the key.