DEV Community

Aqib Javaid
Aqib Javaid

Posted on Originally published at aqibjavaid.site

UPS and FedEx API Integration in Laravel: Real-Time Shipping Rates

If your business ships parcels, showing accurate shipping costs in real time is not optional. Customers abandon carts when shipping is a surprise, and staff waste hours quoting by hand. Both UPS and FedEx publish APIs that return rates for a given package and destination, and wiring them into a Laravel application is very doable. The difficulty is not the first successful request. It is everything that happens when the carrier is slow, the address is wrong or the response format changes.

This guide covers how I approach UPS and FedEx API integration in Laravel, based on building the rate calculation for a logistics quote system. It is part of the broader work I do in API development and integrations.

What the integration needs to do#

Before writing code, be precise about the job. A quote engine has to:

  1. Accept package details (weight, dimensions, quantity) and origin and destination addresses.
  2. Ask each carrier for its available services and prices.
  3. Convert the different responses into one common format.
  4. Show the user the options, and store the quote so it can be sent, accepted or audited later.
  5. Keep working, with a clear message, when a carrier is unavailable.

On the EXPRECO logistics quote system, I built this in Laravel and Livewire, integrating UPS and FedEx APIs to calculate real-time shipping rates from package details and destination, plus a ticketing workflow so businesses could respond to customers and share quotes from the platform.

Step 1: Get credentials and read the docs#

Both carriers require a developer account and application credentials. Their authentication has moved toward OAuth 2.0 client credentials, so check the current documentation rather than relying on older tutorials that describe retired access-key methods. The official portals are the UPS Developer Portal and the FedEx Developer Portal.

Practical notes:

  • Start in the sandbox environment. Rates there may not match production, so validate with real accounts before launch.
  • Decide whether you need published rates or your negotiated account rates. They are different requests with different credentials.
  • Read the terms about caching and displaying rates. Some programmes have rules.

Step 2: Build a carrier abstraction#

Do not scatter HTTP calls through controllers. Create a small interface that every carrier implements:

php

Copy

interface RateProvider
{
    /** @return Collection<int, RateQuote> */
    public function quote(ShipmentRequest $request): Collection;
}
Enter fullscreen mode Exit fullscreen mode

Then write a UpsRateProvider and a FedExRateProvider that translate your ShipmentRequest into each carrier's payload and map the response into a common RateQuote object (carrier, service name, price, currency, estimated delivery, raw reference ID). A third carrier later becomes one new class instead of a rewrite.

Step 3: Handle authentication tokens properly#

OAuth access tokens are short-lived. Request one, cache it in Laravel's cache until shortly before expiry, and reuse it for every call. Requesting a new token on each rate request wastes time and can trigger rate limits.

🛠 Engineering Service

Need specialized help with API Development & Integrations?

Design and integrate reliable APIs connecting your application with third-party platforms, business systems, payment providers, and external data sources.

Discuss Architecture →About this service

php

Copy

$token = Cache::remember('fedex.token', $ttlSeconds - 60, function () {
    return $this->requestNewToken();
});
Enter fullscreen mode Exit fullscreen mode

Wrap the call so that a 401 response clears the cached token and retries once. Keep client IDs and secrets in environment variables, never in the repository.

Step 4: Use Laravel's HTTP client with timeouts and retries#

The HTTP client makes timeouts and retries a one-liner, and it is where reliability starts:

php

Copy

$response = Http::withToken($token)
    ->timeout(8)
    ->retry(2, 200, throw: false)
    ->post($url, $payload);
Enter fullscreen mode Exit fullscreen mode

Choose timeouts that suit the user experience. A checkout page cannot wait 30 seconds. If the carrier does not answer in time, show the other carrier's rates or a fallback, rather than failing the whole page.

Call both carriers in parallel using Http::pool() so the user waits for the slowest one, not the sum of both.

Step 5: Normalise and validate the response#

Carriers return different structures, units and service codes. Map them carefully:

  • Convert currencies and rounding in one place.
  • Translate service codes into names your users understand.
  • Reject responses with missing prices instead of showing zero.
  • Log the raw request and response identifiers so you can debug disputes.

Validate input before you call anyone. Missing postcodes, impossible weights and bad dimensions are the most common causes of carrier errors. Show the user a plain message and let them correct it.

Step 6: Cache sensibly#

Identical requests (same package, same route) within a short window can reuse results. A cache key built from the normalised request and a short TTL cuts calls and speeds up the UI. Keep the TTL short, since rates can change with fuel surcharges and service levels, and check each carrier's terms. My notes on Redis caching architecture in Laravel cover key design and invalidation.

Step 7: Plan for failure#

Carrier APIs go down and return odd errors. Decide up front what the user sees:

  • One carrier fails: show the other's rates, with a note if relevant.
  • Both fail: allow the quote to be saved as "pending" and processed by a queued job, or let staff quote manually.
  • Address problems: surface the carrier's message in plain language.

Also respect carrier rate limits. If many users hit your quote form at once, you may need to queue requests or throttle them. My article on API rate limiting architecture covers the incoming side of the problem, and the same ideas of back-off and limits apply when you are the client.

ℹ️Note

Always store the quote you showed the customer. If the price changes by the time they accept it, you need the original to resolve the dispute.

Step 8: Test without hitting the carrier#

Use Http::fake() to return recorded sample responses in your test suite so tests are fast and deterministic. Keep a handful of real recorded payloads for edge cases: oversized packages, remote areas, multi-package shipments. Add a few sandbox tests you run manually before each release.

Shipping rates vs full shipping#

Rates are the easiest part. Creating labels, tracking shipments, handling customs documents for international orders and managing returns all add separate integrations. Decide on scope early so a "show me rates" project does not quietly become a logistics platform. For an overview of how integration projects are scoped and what influences their cost, read API integration cost and timeline, and for general design rules see third-party API integration best practices.

Frequently asked questions#

Should I use a shipping aggregator instead of direct carrier APIs?#

An aggregator gives you many carriers through one integration and may include labels and tracking. The trade-off is fees and less control. Direct APIs make sense when you use one or two carriers and want negotiated rates and full control.

Do I need Livewire or a frontend framework?#

No. The integration is backend work. Livewire is convenient for live quote forms, but a standard form or a JavaScript client works equally well.

How accurate are the rates?#

Accuracy depends on the data you send and the account credentials used. Wrong dimensions or addresses produce wrong prices, and surcharges may appear later. Validate input and store the original quote.

How long does an integration like this take?#

A single carrier's rate lookup is usually days of work. Two carriers with a clean abstraction, error handling and tests is more. Labels and tracking add more again.

Key takeaways#

  • Wrap each carrier behind a common interface and return one normalised quote format.
  • Cache OAuth tokens, set timeouts, retry sensibly and call carriers in parallel.
  • Decide what happens when a carrier fails before you launch.
  • Store every quote shown to a customer, and test with faked responses.

If you need rates or full shipping integrated into your application, book a call or get in touch.


Originally published at aqibjavaid.site.

Top comments (0)