Capture Final URLs After Redirects with Guzzle TransferStats

📖 2 minutes read

Need to know the final URL after following redirects? Don’t parse the response—use Guzzle’s TransferStats callback to capture the effective URI automatically.

The Use Case

You’re processing shortened URLs (like bit.ly links) and need to extract the final slug or canonical URL after all redirects resolve.

The Solution

use GuzzleHttp\TransferStats;
use Illuminate\Support\Facades\Http;

$effectiveUrl = null;

Http::withOptions([
    'on_stats' => function (TransferStats $stats) use (&$effectiveUrl) {
        $effectiveUrl = (string) $stats->getEffectiveUri();
    },
])->head($shortenedUrl);

// $effectiveUrl is now 'https://example.com/articles/full-guide'
$slug = basename(parse_url($effectiveUrl, PHP_URL_PATH));
// $slug = 'full-guide'

How It Works

  1. on_stats is a Guzzle option that registers a callback
  2. The callback runs after the request completes
  3. TransferStats contains metadata about the request, including the final URI
  4. getEffectiveUri() returns the URL after all redirects

Why HEAD Instead of GET?

Using head() instead of get() makes this efficient—you only need the final destination, not the response body. The server follows redirects and returns headers, but skips sending the full content.

Result: Fast, bandwidth-efficient redirect resolution.

What Else Is in TransferStats?

Http::withOptions([
    'on_stats' => function (TransferStats $stats) {
        dump([
            'effective_uri' => $stats->getEffectiveUri(),
            'transfer_time' => $stats->getTransferTime(),  // seconds
            'redirect_count' => $stats->getRedirectCount(),
        ]);
    },
])->head($url);

You can also access:

  • getHandlerStats() — low-level curl stats (DNS lookup time, connect time, etc.)
  • getRequest() — the original request object
  • getResponse() — the final response object (if available)

Real-World Applications

  • Canonical URL discovery: Resolve short links to full URLs for storage
  • Slug extraction: Extract clean slugs from user-submitted URLs
  • Redirect chain analysis: Track how many hops it took to reach the final destination
  • Performance monitoring: Log transfer times and redirect counts for external APIs

Bonus: Caching Redirects

Since redirect resolution is deterministic, you can cache the effective URL:

$cacheKey = 'redirect_'.md5($shortenedUrl);

return Cache::remember($cacheKey, now()->addDay(), function () use ($shortenedUrl) {
    $effectiveUrl = null;
    Http::withOptions([
        'on_stats' => fn(TransferStats $stats) => 
            $effectiveUrl = (string) $stats->getEffectiveUri()
    ])->head($shortenedUrl);
    
    return $effectiveUrl;
});

Now you only resolve each shortened URL once, even across multiple requests.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *