# How to Integrate a Third-Party Logistics API for Shipping and Tracking in Flu...

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 60-90 min
- Compatibility: FlutterFlow Free+
- Last updated: March 2026

## TL;DR

Integrate a logistics API in FlutterFlow by connecting to ShipEngine or EasyPost via Cloud Functions. The Cloud Function validates addresses, fetches rate quotes across multiple carriers, creates shipping labels, and returns the label PDF URL and tracking number. FlutterFlow displays the rate comparison and triggers the label purchase when the user selects a shipping option. Store label URLs and tracking numbers in Firestore for later retrieval.

## Rate shopping, label generation, and tracking via ShipEngine in one FlutterFlow workflow

A third-party logistics API handles the most complex part of an e-commerce or delivery app: comparing shipping rates across carriers (FedEx, UPS, USPS, DHL) and creating the physical shipping label. Rebuilding carrier API connections individually is months of work. ShipEngine and EasyPost normalize all carrier interactions into a single API, handling rate shopping, label generation, address validation, and tracking in one integration. A Cloud Function proxies these calls (keeping your API keys server-side), and FlutterFlow displays the rate options and handles the user's selection with a purchase action that writes the label data to Firestore.

## Before you start

- A FlutterFlow project with Firebase configured (Settings → Project Setup → Firebase)
- A ShipEngine account with at least one carrier connected (shipengine.com — connect USPS free via Stamps.com)
- Cloud Functions enabled on Firebase (Blaze plan required)
- An orders Firestore collection with origin and destination address fields

## Step-by-step guide

### 1. Set up ShipEngine and configure carrier accounts

Create a ShipEngine account at shipengine.com. From the API Keys section, copy your API key. In your Firebase functions directory, set the key in environment variables: firebase functions:config:set shipengine.key='SE-YOUR_KEY'. In the ShipEngine dashboard, connect carrier accounts. ShipEngine provides a built-in test USPS account for development. To add FedEx or UPS, you need existing carrier accounts with those providers — connect them in ShipEngine's Carriers section. Each connected carrier appears automatically in rate quotes. You do not need separate API accounts for each carrier once they are connected to ShipEngine.

**Expected result:** ShipEngine API key is stored in Cloud Function environment. At least one carrier is connected and available for rate quotes.

### 2. Create the address validation Cloud Function

Create a Cloud Function named validateAddress. It accepts to_name, to_company, address_line1, address_line2, city_locality, state_province, postal_code, country_code as query or body parameters. Call ShipEngine's address validation endpoint: POST https://api.shipengine.com/v1/addresses/validate with header API-Key. ShipEngine returns status: verified, unverified, or error, plus any corrections to the address fields (normalized street name, city, state). Return the validated address to FlutterFlow. Show any corrections to the user for confirmation: 'We normalized your address to: 123 Main St Apt 4 → 123 MAIN ST STE 4. Is this correct?' This prevents shipping label creation with invalid addresses.

**Expected result:** Users see a confirmation prompt if their address was normalized, and are blocked from proceeding with unverifiable addresses.

### 3. Create the rate quote Cloud Function

Create a Cloud Function named getRates. It accepts shipFrom and shipTo address objects, weight (in ounces or grams), dimensions (length, width, height), and a list of carrier IDs. Call ShipEngine POST /v1/rates with the shipment object. ShipEngine returns an array of rate objects across all connected carriers and service levels, each with: carrier_id, carrier_friendly_name, service_code, service_type, shipping_amount.amount (price), delivery_days, estimated_delivery_date, trackable. Filter out rates with error_messages. Sort by shipping_amount ascending. Return the top 5-10 rates. In FlutterFlow, create an API Group pointing to this Cloud Function and display results in a ListView — each rate row shows carrier logo icon, service name, delivery days, and price.

```
const functions = require('firebase-functions');
const axios = require('axios');

const API_KEY = functions.config().shipengine.key;

exports.getRates = functions.https.onRequest(async (req, res) => {
  res.set('Access-Control-Allow-Origin', '*');
  if (req.method === 'OPTIONS') return res.status(204).send('');

  const { shipFrom, shipTo, weight, dimensions } = req.body;
  if (!shipFrom || !shipTo || !weight) {
    return res.status(400).json({ error: 'shipFrom, shipTo, and weight required' });
  }

  try {
    const { data } = await axios.post(
      'https://api.shipengine.com/v1/rates',
      {
        rate_options: { carrier_ids: [], calculate_tax_amount: false },
        shipment: {
          ship_from: shipFrom,
          ship_to: shipTo,
          packages: [{ weight: { value: weight, unit: 'ounce' }, dimensions }],
        },
      },
      { headers: { 'API-Key': API_KEY, 'Content-Type': 'application/json' } }
    );

    const rates = (data.rate_response?.rates || [])
      .filter((r) => !r.error_messages?.length)
      .sort((a, b) => a.shipping_amount.amount - b.shipping_amount.amount)
      .slice(0, 10)
      .map((r) => ({
        rateId: r.rate_id,
        carrier: r.carrier_friendly_name,
        service: r.service_type,
        price: r.shipping_amount.amount,
        currency: r.shipping_amount.currency,
        deliveryDays: r.delivery_days,
        estimatedDelivery: r.estimated_delivery_date,
      }));

    res.json({ rates });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});
```

**Expected result:** A ListView shows shipping options from multiple carriers sorted by price. Each row displays carrier name, service level, estimated delivery, and cost.

### 4. Create the label purchase Cloud Function and store in Firestore

Create a Cloud Function named purchaseLabel. It accepts the rate_id (from the rate quote), shipFrom and shipTo addresses, and orderId (to link the label to the Firestore order). Call ShipEngine POST /v1/labels/rates/{rate_id} to purchase the label for the selected rate. ShipEngine charges the carrier account for the label cost. The response includes label_id, tracking_number, label_download.pdf (URL to the PDF label), label_download.png (URL to PNG preview), and shipment_cost. Write this to Firestore orders/{orderId}: set shippingLabel.trackingNumber, shippingLabel.labelPdfUrl, shippingLabel.carrier, shippingLabel.service, shippingLabel.cost, shippingLabel.purchasedAt. Return the label URL and tracking number to FlutterFlow.

**Expected result:** The shipping label is purchased from the carrier. The label PDF URL and tracking number are stored on the Firestore order document and returned to the app.

### 5. Build the complete shipping workflow in FlutterFlow

On your order detail page, add a Create Shipping Label button visible only for orders without a label yet. The button opens a Bottom Sheet with a shipping form: weight TextField, dimensions TextFields (length × width × height), and the destination address (pre-filled from the order if available). Add a Get Rates button that calls the validateAddress function first, then getRates with the form inputs. Bind the results to a ListView of rate options. Each rate row has a Select button. On Select, show a Confirm Shipment dialog showing carrier, service, cost, and estimated delivery. On Confirm, call purchaseLabel. On success, show a Bottom Sheet with the tracking number and a Download Label button that calls Launch URL on the label PDF URL.

**Expected result:** The complete label workflow is available on the order page: validate address, get rates, select option, confirm and purchase, download label, and retrieve tracking number.

## Complete code example

File: `shipping_workflow_schema.txt`

```text
Cloud Functions:
├── validateAddress
│   ├── Input: full address fields
│   └── Output: { status, normalizedAddress, messages }
│
├── getRates
│   ├── Input: { shipFrom, shipTo, weight, dimensions }
│   └── Output: { rates: [{ rateId, carrier, service, price, deliveryDays }] }
│
└── purchaseLabel
    ├── Input: { rateId, shipFrom, shipTo, orderId }
    └── Output: { trackingNumber, labelPdfUrl, labelPngUrl, cost }

Firestore: orders/{orderId}
├── ...existing order fields...
└── shippingLabel (Map)
    ├── trackingNumber: String
    ├── labelPdfUrl: String
    ├── labelPngUrl: String
    ├── carrier: String
    ├── service: String
    ├── cost: Double
    └── purchasedAt: Timestamp

FlutterFlow Page: OrderDetail
├── Backend Query: orders/{orderId}
│
├── Conditional: if shippingLabel == null
│   └── Button: 'Create Shipping Label'
│       └── Open Bottom Sheet: ShippingForm
│
├── Conditional: if shippingLabel != null
│   ├── Text: 'Tracking: ' + trackingNumber
│   ├── Text: 'Carrier: ' + carrier + ' ' + service
│   ├── Text: 'Label cost: $' + cost
│   └── Button: 'Download Label' → Launch URL (labelPdfUrl)
│
Bottom Sheet: ShippingForm
├── TextField: weight (oz)
├── Row: length × width × height TextFields
├── Button: 'Get Rates' → call validateAddress + getRates
├── ListView: rate options
│   └── Row: carrier + service + '$price' + deliveryDays + Select button
└── Confirm Dialog: carrier + service + cost + delivery date
```

## Common mistakes

- **Creating a shipping label without validating the address first** — Invalid addresses generate labels that carriers refuse to accept, resulting in returned packages, wasted label costs ($5-20 per label), and frustrated customers. Address validation is a required step before any label purchase. Fix: Always call the validateAddress endpoint before getRates. If the status is not 'verified', show the user the issues or normalized address for confirmation before proceeding. Block label creation for unresolvable addresses.
- **Exposing the ShipEngine API key in FlutterFlow's API Manager headers** — ShipEngine API keys have billing authority — anyone who extracts the key from the compiled app can purchase shipping labels charged to your carrier accounts. Fix: All ShipEngine calls must go through Cloud Functions with the key stored in Firebase environment variables. FlutterFlow only calls your Cloud Function URLs, never ShipEngine directly.
- **Not storing the label PDF URL in Firestore after purchase** — Label PDF URLs from ShipEngine are temporary (typically expire after 24-48 hours). If you only display them to the user at purchase time and do not store them, the label becomes inaccessible for reprinting. Fix: After purchasing the label, download the PDF and upload it to Firebase Storage via the Cloud Function. Store the permanent Firebase Storage URL in Firestore. The ShipEngine URL should be treated as temporary.

## Best practices

- Use ShipEngine's test mode with test carrier credentials during development to avoid purchasing real labels while building the integration
- Store the ShipEngine rate_id when displaying rates — it is required to purchase the label and expires after a short window (typically 15-30 minutes), so prompt users to purchase promptly
- Display the carrier's transit time as 'Estimated delivery: Thu, Apr 3' rather than '3 business days' — specific dates are clearer to customers
- Implement a void label endpoint to cancel labels that were purchased in error — ShipEngine provides POST /v1/labels/{id}/void within a window before the label is used
- Log all label purchases to a Firestore shipping_log collection for accounting and dispute resolution
- Add insurance options to the label purchase form for high-value shipments — ShipEngine supports declared value and carrier insurance
- Automate label generation for new orders via a Firestore onCreate trigger Cloud Function rather than requiring manual action for high-volume operations

## Frequently asked questions

### Which logistics API should I use for shipping labels in FlutterFlow — ShipEngine or EasyPost?

Both are excellent choices. ShipEngine has a more generous free tier (500 free label purchases per month for USPS) and is easier to set up for beginners. EasyPost has a slightly simpler API and offers better international carrier coverage. Both normalize all carrier interactions into a consistent format. ShipEngine is recommended for new integrations due to its extensive documentation.

### Do I need FedEx and UPS accounts to use their shipping rates?

Yes, you need active carrier accounts with FedEx and UPS to use their live rates and purchase labels. Both require business accounts. ShipEngine and EasyPost negotiate discounted rates that are typically better than retail — when you connect your carrier accounts, you get these negotiated rates automatically. USPS can be connected without a separate account via ShipEngine's built-in Stamps.com integration.

### How do I print the shipping label from a FlutterFlow app?

ShipEngine returns a label PDF URL. Use Launch URL to open it — most devices will prompt to open in a PDF viewer or browser where it can be printed or shared. For direct printing, use a Custom Action with the printing Flutter package which can print to nearby AirPrint or Google Cloud Print printers directly from the app.

### Can I generate international shipping labels for shipments outside the US?

Yes. ShipEngine and EasyPost both support international shipments. For international labels, you also need to provide customs declaration information: commodity description, HS tariff code, declared value, and country of origin. These are required fields for DHL, FedEx International, and UPS International labels.

### How do I void a shipping label if it was created by mistake?

ShipEngine provides a void endpoint: POST /v1/labels/{label_id}/void. Call this within the void window (varies by carrier, typically up to 24 hours before the package is scanned). Add a Void Label button to your order detail page visible only for labels less than 24 hours old. Voiding refunds the label cost to your carrier account.

### What if I need a fully automated shipping workflow for high-volume orders?

High-volume shipping automation — auto-selecting the cheapest rate per order, bulk label generation, batch printing, carrier pickup scheduling, and customs documentation for international orders — requires significant Cloud Function architecture beyond the basic integration. RapidDev has built production logistics workflows in FlutterFlow for e-commerce businesses processing thousands of shipments per month.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-integrate-a-third-party-logistics-api-for-shipping-and-tracking-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-integrate-a-third-party-logistics-api-for-shipping-and-tracking-in-flutterflow
