Exchange rates in PHP and Laravel

Fetch and cache live exchange rates with Laravel’s HTTP client, bearer authentication, bounded timeouts, and freshness metadata.

exchangerate.devJul 17, 20266 min read

A small Laravel service can call exchangerate.dev, reject failed HTTP responses, and cache a complete FX observation for 60 seconds. Keep the API key in configuration and return source, market_session, and data_updated_at with the rates.

Key points
Use Laravel’s HTTP client with bearer authentication, an explicit timeout, and throw() for non-success responses.
Cache the complete successful response for 60 seconds to stay well inside the Free tier’s 10 requests per minute.
Keep the key in config/services.php through EXCHANGERATE_API_KEY, not in application code.
Use a decimal library for calculations that require exact money arithmetic; PHP floats are binary floating point.

Build one cached FX service

The service below creates a stable cache key from the base and symbol list. The callback stores only a successful JSON response because throw() raises before Laravel writes a failed request into the cache.

php · app/Services/ExchangeRateService.php
<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

final class ExchangeRateService
{
    public function latest(string $base, array $symbols): array
    {
        sort($symbols);
        $symbolList = implode(',', $symbols);
        $cacheKey = "fx:latest:{$base}:{$symbolList}";

        return Cache::remember($cacheKey, now()->addSeconds(60), function () use (
            $base,
            $symbolList,
        ): array {
            return Http::acceptJson()
                ->withToken(config('services.exchangerate.key'))
                ->timeout(5)
                ->get(
                    "https://api.exchangerate.dev/v1/latest/{$base}",
                    ['symbols' => $symbolList],
                )
                ->throw()
                ->json();
        });
    }
}

Configure the key outside source control

php · config/services.php and .env
// config/services.php
'exchangerate' => [
    'key' => env('EXCHANGERATE_API_KEY'),
],

// .env (never commit the real value)
EXCHANGERATE_API_KEY=exr_live_...

Read the rate and its observation fields

Call $service->latest("USD", ["EUR", "GBP"]), then read $result["rates"]["EUR"]. Keep $result["source"], $result["market_session"], and $result["data_updated_at"] in the view model or stored record so the value never loses its context.

Do not cache errors as rates
A 401 means the key or header is wrong. A 429 means the application has exceeded a request limit. Let both fail, back off where appropriate, and keep any prior successful observation only with its original update time.
  • Sort symbols before building the cache key.
  • Set a bounded HTTP timeout.
  • Call throw before reading and caching JSON.
  • Round the final amount for display, not the API rate.
ER
exchangerate.dev
Integration guides for developers building with FX data.

Keep reading