# How to Integrate Lovable with Shippo

- Tool: Lovable
- Difficulty: Intermediate
- Time required: 35 minutes
- Last updated: March 2026

## TL;DR

To integrate Shippo with Lovable, create a Supabase Edge Function that proxies Shippo REST API calls using your Shippo API token stored in Cloud Secrets. This lets you build multi-carrier rate comparisons, shipping label generators, and tracking dashboards in Lovable — with discounted USPS, UPS, FedEx, and DHL rates — without exposing your token in frontend code. Setup takes about 35 minutes.

## Add Multi-Carrier Shipping Rates and Label Generation to Your Lovable App with Shippo

Shippo simplifies shipping integration by aggregating 85+ carriers — USPS, UPS, FedEx, DHL Express, Canada Post, and more — into a single API. Instead of building separate integrations for each carrier, you call Shippo once and get back a comparison of available rates across all carriers your account is configured to use. Shippo also negotiates discounted rates with major carriers, meaning the rates your customers see through your Lovable app can be lower than the carriers' published prices.

The integration follows Lovable's standard security pattern: all Shippo API calls route through a Supabase Edge Function. Your Shippo API token lives encrypted in Cloud → Secrets. The Edge Function receives rate request or label creation instructions from your React frontend, forwards them to Shippo's REST API at goshippo.com/api/, and returns the results. Shippo's API is synchronous for rates and label generation — unlike freight APIs, you get immediate results without polling.

Shippo differs from ShipStation in its focus: Shippo is API-first and developer-oriented, designed to be embedded into apps that need shipping functionality as a feature. ShipStation is a standalone shipping management platform for high-volume sellers who want a full operations dashboard. If you're building an e-commerce app in Lovable and need to add shipping to checkout, or building an order management tool that needs to print labels, Shippo is the right choice. The REST API covers the full shipping workflow: create a shipment (addresses + parcel), get rates, purchase a label for the chosen rate, and track the resulting shipment.

## Before you start

- A Shippo account — sign up free at goshippo.com (no credit card required for test mode, live shipments require billing info)
- Your Shippo API token from the Shippo dashboard under API → API Keys
- A Lovable project with Lovable Cloud enabled (the default for all new projects)
- Origin address details for your shipping origin (warehouse or home address) — needed for all rate calculations
- Basic understanding of shipping terminology: origin address, destination address, parcel dimensions (length/width/height), weight in ounces (Shippo uses ounces for US carriers)

## Step-by-step guide

### 1. Get your Shippo API token

Log in to your Shippo account at goshippo.com. In the left navigation, click API under the Settings section. You'll see two types of tokens: a Test token (starting with 'shippo_test_...') and a Live token (starting with 'shippo_live_...'). The test token lets you create shipments and get rates without purchasing real labels or spending money — use this during development. The live token processes real label purchases charged to your Shippo account balance.

Copy your Test token for now — you'll use it for all development and testing. The Shippo API is version-controlled; the current version is v1 and the base URL is https://api.goshippo.com/. Shippo's test mode returns real rate data (actual carrier prices) but label purchases with the test token create test labels that cannot be used for actual shipping.

Shippo's API follows RESTful conventions with JSON request and response bodies. Authentication is via HTTP Basic Auth where the API token is the username and the password is an empty string. The Edge Function in this guide handles this authentication automatically using the token from Cloud Secrets. Once you have your test token, you're ready to configure Cloud Secrets.

**Expected result:** You have a Shippo test token (shippo_test_...) copied from the Shippo dashboard API settings page.

### 2. Store your Shippo token in Cloud Secrets

Navigate to your Lovable project and click the '+' icon next to the Preview panel at the top of the screen to open the Cloud tab. Click Secrets in the left-hand navigation of the Cloud tab. Click Add Secret to add a new encrypted environment variable.

Set the name to SHIPPO_API_TOKEN and paste your Shippo test token (shippo_test_...) as the value. Click Save. The token is now encrypted and stored — it will only be accessible from your Edge Functions via Deno.env.get('SHIPPO_API_TOKEN').

When you're ready to go live with real label purchases, you'll update this secret value to your live token (shippo_live_...). There's no code change required — only the secret value changes. This is why storing the token as a secret rather than hardcoding it in the Edge Function is so important: switching from test to production is a single secret update rather than a code deployment.

Never paste your Shippo API token into Lovable's chat prompt. Free-tier Lovable accounts have publicly visible chat history, and tokens pasted in chat are recoverable from Git commit history. Lovable blocks approximately 1,200 hardcoded API keys daily — always use Cloud → Secrets for any token that authenticates an external service.

**Expected result:** Cloud → Secrets shows SHIPPO_API_TOKEN as an encrypted entry. The value shows only as encrypted dots, confirming it's securely stored.

### 3. Create the Shippo Edge Function proxy

Now create the Edge Function that proxies Shippo API calls. Type the prompt below into Lovable's chat interface. Lovable will generate the TypeScript code at supabase/functions/shippo-proxy/index.ts and deploy it to your Cloud project.

The Edge Function uses HTTP Basic Auth with the Shippo API token as the username and an empty string as the password. It accepts an 'action' query parameter to route between different Shippo operations: 'rates' creates a Shippo Shipment object and returns available rates, 'label' purchases a label for a specific rate object, and 'track' retrieves tracking information for a given carrier and tracking number.

For rate requests, the Edge Function creates a Shippo Shipment — combining the sender address, recipient address, and parcel (weight + dimensions) — in a single POST. Shippo processes the shipment synchronously and returns all available rates immediately. For label generation, a separate POST to /transactions purchases the selected rate and returns the label URL and tracking number. All responses include CORS headers for browser compatibility.

```
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'

const SHIPPO_BASE = 'https://api.goshippo.com'

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  try {
    const apiToken = Deno.env.get('SHIPPO_API_TOKEN')

    if (!apiToken) {
      return new Response(
        JSON.stringify({ error: 'SHIPPO_API_TOKEN not configured in Cloud Secrets' }),
        { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    // Shippo uses HTTP Basic Auth: token as username, empty password
    const authHeader = 'Basic ' + btoa(`${apiToken}:`)
    const url = new URL(req.url)
    const action = url.searchParams.get('action')

    const shippoHeaders = {
      'Authorization': authHeader,
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    }

    if (action === 'rates') {
      const body = await req.text()
      // Create shipment with async=false to get rates synchronously
      const shipmentBody = JSON.parse(body)
      shipmentBody.async = false

      const shipmentRes = await fetch(`${SHIPPO_BASE}/shipments/`, {
        method: 'POST',
        headers: shippoHeaders,
        body: JSON.stringify(shipmentBody),
      })

      const shipmentData = await shipmentRes.json()

      return new Response(JSON.stringify(shipmentData), {
        status: shipmentRes.status,
        headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      })
    } else if (action === 'label') {
      const body = await req.text()
      const labelRes = await fetch(`${SHIPPO_BASE}/transactions/`, {
        method: 'POST',
        headers: shippoHeaders,
        body,
      })
      const labelData = await labelRes.json()
      return new Response(JSON.stringify(labelData), {
        status: labelRes.status,
        headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      })
    } else if (action === 'track') {
      const carrier = url.searchParams.get('carrier')
      const trackingNumber = url.searchParams.get('trackingNumber')
      const trackRes = await fetch(`${SHIPPO_BASE}/tracks/${carrier}/${trackingNumber}`, {
        headers: shippoHeaders,
      })
      const trackData = await trackRes.json()
      return new Response(JSON.stringify(trackData), {
        status: trackRes.status,
        headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      })
    } else {
      return new Response(
        JSON.stringify({ error: 'Invalid action. Use: rates, label, or track' }),
        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }
  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
    )
  }
})
```

**Expected result:** Lovable deploys the shippo-proxy Edge Function to Cloud. The function handles rate calculation, label purchasing, and tracking in a single serverless endpoint.

### 4. Build the rate comparison and label generation UI

With the Edge Function deployed, ask Lovable to build the shipping rate comparison component. This component collects sender address, recipient address, and parcel details from the user (or pre-fills them from order data), calls the Edge Function to get Shippo rates, and displays the carrier options in a comparison layout.

Shippo's Shipment API requires address objects with name, street1, city, state, zip, country (ISO 2-letter code), and phone. Parcel objects require length, width, height (in inches for US carriers), weight (in ounces for USPS, pounds for UPS/FedEx — Shippo handles conversion), distance_unit ('in'), and mass_unit ('oz' or 'lb'). Lovable will generate TypeScript interfaces for these and build a properly typed form.

Rate results include the carrier name (e.g., 'USPS'), service level name ('Priority Mail'), price as a string ('8.40'), currency ('USD'), estimated transit days, and an object_id used to purchase the label. The label generation step uses this object_id to POST to /transactions, which returns a label_url PDF link.

For more complex configurations involving international customs, return label workflows, or high-volume batch label printing, RapidDev's team can help design the right Edge Function architecture for your specific fulfillment workflow.

**Expected result:** The rate comparison component renders in Lovable preview. Entering a test address and package details shows carrier rate cards. Selecting a rate and confirming generates a Shippo test label with a download link.

## Best practices

- Always set 'async': false in Shipment API requests during the rate display step — synchronous shipment creation returns rates immediately without requiring a polling loop.
- Store the Shippo Shipment object_id in Supabase when you create it so you can re-fetch rates without creating a new shipment if the user wants to change their selection.
- Validate addresses using Shippo's Address Validation API before creating shipments — invalid addresses that pass validation but fail at label generation waste an API call and confuse users.
- Use Shippo's webhook feature to receive automatic tracking updates rather than polling the tracking endpoint — configure the webhook URL to point to a separate Edge Function that updates shipment status in Supabase.
- Display carrier logo images alongside rate cards using Shippo's carrier slugs (e.g., 'usps', 'ups', 'fedex') to map to logo assets — visual differentiation helps customers recognize carriers.
- Never show the raw Shippo label URL directly in the browser as an img tag — some label URLs expire. Instead, download the label blob in your Edge Function and store it in Supabase Storage for persistent access.
- For international shipments, collect customs declaration information (item description, HS code, value, country of origin) before the label generation step — Shippo requires this for international labels and missing it causes label creation to fail.

## Use cases

### Shipping rate calculator at checkout

Show customers live shipping rates from multiple carriers at checkout based on their address, package weight, and dimensions. Display carrier options with estimated delivery dates so customers can choose between cheapest and fastest. Shippo returns discounted rates that are often lower than carrier websites.

Prompt example:

```
Create a shipping rate calculator that calls a shippo-proxy Edge Function to get Shippo rates. Take the customer's shipping address, package weight in oz, and dimensions in inches. Create a Shippo Shipment object via the Edge Function with my warehouse address as origin and the customer address as destination. Display each rate option as a card with carrier name, service level, price, and estimated delivery days. Let the customer select a rate and save their choice to the order in Supabase.
```

### Shipping label generator for order fulfillment

Allow store admins to generate and print Shippo shipping labels directly from your order management page in Lovable. Select the cheapest rate or a specific carrier, purchase the label via Shippo API, and display a PDF label ready to print or download.

Prompt example:

```
Add a Create Label button to my orders table. When clicked for an order, open a modal that shows available Shippo rates for that order's weight and destination. When the admin selects a rate and confirms, call the shippo-proxy Edge Function to purchase the Shippo label using that rate's rateId. Display the returned label URL as a Download PDF Label link and save the tracking number to the order record in Supabase.
```

### Package tracking page for customers

Build a self-service tracking page where customers enter their order number or tracking number and see real-time shipment status. The Edge Function calls Shippo's Tracking API and returns a timeline of events from the carrier.

Prompt example:

```
Create a package tracking page where customers can enter a Shippo tracking number and carrier. Call the shippo-proxy Edge Function to fetch tracking status from the Shippo Tracking API. Display a timeline of shipment events — each with date, time, location, and status description. Show the current status prominently at the top with estimated delivery. Add a way to look up tracking by order number using the tracking numbers stored in Supabase.
```

## Troubleshooting

### Shippo returns a 401 Unauthorized error

Cause: The HTTP Basic Auth header is malformed, or the SHIPPO_API_TOKEN value in Cloud Secrets contains extra whitespace or is truncated.

Solution: Verify the SHIPPO_API_TOKEN secret in Cloud → Secrets contains the full token string (shippo_test_... or shippo_live_...) with no extra spaces. The Basic Auth header should encode 'token:' (token followed by colon and empty password) using btoa(). Re-enter the secret if the value looks suspicious. Test with curl from the Shippo dashboard's API testing page to confirm your token is valid independently of the Edge Function.

```
// Correct Shippo Basic Auth construction
const authHeader = 'Basic ' + btoa(`${apiToken}:`) // Note the colon after token, empty password
```

### Rate request returns 'Invalid address' or 'Address validation failed'

Cause: The address object is missing required fields, or the country code is incorrect. Shippo requires ISO 3166-1 alpha-2 country codes and US addresses must include a valid state abbreviation.

Solution: Ensure the address_from and address_to objects include all required fields: name, street1, city, state (2-letter US state abbreviation), zip, and country ('US' not 'USA'). For international addresses, state can be empty string but country must be the correct ISO 2-letter code. Set 'validate': true in the address object to get detailed validation errors from Shippo.

```
// Correct address structure for Shippo
const address = {
  name: 'Jane Doe',
  street1: '123 Main St',
  city: 'New York',
  state: 'NY',      // 2-letter state code for US
  zip: '10001',
  country: 'US',    // ISO 2-letter, NOT 'USA'
  validate: true    // Get detailed validation errors
}
```

### Label purchase returns 'Insufficient funds' or transaction stays in QUEUED status

Cause: Your Shippo live account doesn't have a payment method on file, or you're using the test token to purchase a live label.

Solution: For test mode development, use your test token (shippo_test_...) — test labels are free and do not charge your account. For live label purchases, ensure your Shippo account has a valid credit card or prepaid balance. Go to Shippo dashboard → Billing to add a payment method. Switch to your live token by updating the SHIPPO_API_TOKEN secret when moving to production.

### Tracking endpoint returns 'Tracking not available' for a newly created label

Cause: Shippo tracking data is only available after the carrier scans the package at pickup. A freshly generated test label has no tracking events yet.

Solution: Test tracking with a real tracking number from a previously shipped package. For automated testing, Shippo's documentation provides sample tracking numbers that return mock tracking events. In production, tracking data becomes available within a few hours of carrier pickup — implement polling or display 'Tracking not yet available — check back later' for labels less than a few hours old.

## Frequently asked questions

### Does Shippo really offer discounted shipping rates?

Yes. Shippo negotiates pre-discounted rates with USPS, UPS, DHL Express, and other carriers that are often 15-30% below retail prices. These discounts are available to all Shippo accounts at no extra cost — they're built into the API. The discounts are most significant for USPS Priority Mail and DHL Express, where Shippo's commercial rates can save meaningful amounts per shipment compared to buying labels directly from the carrier.

### What is the difference between Shippo and ShipStation?

Shippo is an API-first platform designed to be embedded as a feature within applications — you use the Shippo API to add shipping rate comparison and label generation to your own app. ShipStation is a standalone shipping management dashboard where you manage orders, print labels, and track shipments as the primary activity. Use Shippo if you're building an app that needs shipping as a feature; use ShipStation if you want a dedicated tool for managing your shipping operations.

### Does Shippo support international shipping?

Yes. Shippo supports international shipping through carriers like USPS International, UPS International, DHL Express, and FedEx International. International shipments require customs declarations with item descriptions, HS codes, declared values, and country of origin. Shippo's API accepts these customs details as part of the Shipment object and includes them in the generated label and customs forms.

### How does Shippo test mode work?

Shippo test mode (using your shippo_test_... token) lets you create shipments, get real carrier rates, and generate test labels without purchasing real postage. Test labels have a 'SAMPLE' watermark and cannot be used for actual shipments. Rate quotes in test mode are real carrier rates, making test mode useful for validating your rate calculator shows accurate prices before going live.

### Can I use Shippo to create return labels?

Yes. Shippo supports return label generation by creating a Shipment with the address_from and address_to reversed (customer address as origin, your warehouse as destination). You can generate return labels proactively (include with the outbound shipment) or on-demand (generate when a customer requests a return). Set the is_return field to true in the Shipment object to identify it as a return for reporting purposes.

---

Source: https://www.rapidevelopers.com/lovable-integration/shippo
© RapidDev — https://www.rapidevelopers.com/lovable-integration/shippo
