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.
Integration method
Shippo integration in Lovable works by routing all API calls through Supabase Edge Functions running on Deno. Your Shippo API token is stored as an encrypted secret in Cloud → Secrets and accessed via Deno.env.get() — never exposed to the browser. The React frontend calls the Edge Function, which authenticates with the Shippo REST API and returns carrier rates, shipping labels, and tracking information.
Prerequisites
- 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
Get your Shippo API token
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.
Pro tip: Shippo automatically provides pre-negotiated discounted rates for USPS, UPS, and DHL Express when you use your live token and have verified your account. These discounts can be 15-30% below retail carrier prices and are available to all Shippo accounts regardless of shipping volume.
Expected result: You have a Shippo test token (shippo_test_...) copied from the Shippo dashboard API settings page.
Store your Shippo token in Cloud Secrets
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.
Pro tip: Add a second secret SHIPPO_ENV with value 'test' during development, then change it to 'live' for production. Your Edge Function can use this to log which mode it's operating in, making debugging easier.
Expected result: Cloud → Secrets shows SHIPPO_API_TOKEN as an encrypted entry. The value shows only as encrypted dots, confirming it's securely stored.
Create the Shippo Edge Function proxy
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.
Create a Supabase Edge Function at supabase/functions/shippo-proxy/index.ts that proxies requests to the Shippo REST API at https://api.goshippo.com/. Use Deno.env.get('SHIPPO_API_TOKEN') for HTTP Basic Auth (token as username, empty string as password). Support three actions via query parameter: 'rates' (POST a Shipment to /shipments with address_from, address_to, and parcels in the request body — return the rates array), 'label' (POST to /transactions with the selected rate object_id in the request body), and 'track' (GET /tracks/{carrier}/{trackingNumber}). Include CORS headers in all responses.
Paste this in Lovable chat
1import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'23const SHIPPO_BASE = 'https://api.goshippo.com'45const corsHeaders = {6 'Access-Control-Allow-Origin': '*',7 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',8}910serve(async (req) => {11 if (req.method === 'OPTIONS') {12 return new Response('ok', { headers: corsHeaders })13 }1415 try {16 const apiToken = Deno.env.get('SHIPPO_API_TOKEN')1718 if (!apiToken) {19 return new Response(20 JSON.stringify({ error: 'SHIPPO_API_TOKEN not configured in Cloud Secrets' }),21 { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }22 )23 }2425 // Shippo uses HTTP Basic Auth: token as username, empty password26 const authHeader = 'Basic ' + btoa(`${apiToken}:`)27 const url = new URL(req.url)28 const action = url.searchParams.get('action')2930 const shippoHeaders = {31 'Authorization': authHeader,32 'Content-Type': 'application/json',33 'Accept': 'application/json',34 }3536 if (action === 'rates') {37 const body = await req.text()38 // Create shipment with async=false to get rates synchronously39 const shipmentBody = JSON.parse(body)40 shipmentBody.async = false4142 const shipmentRes = await fetch(`${SHIPPO_BASE}/shipments/`, {43 method: 'POST',44 headers: shippoHeaders,45 body: JSON.stringify(shipmentBody),46 })4748 const shipmentData = await shipmentRes.json()4950 return new Response(JSON.stringify(shipmentData), {51 status: shipmentRes.status,52 headers: { ...corsHeaders, 'Content-Type': 'application/json' },53 })54 } else if (action === 'label') {55 const body = await req.text()56 const labelRes = await fetch(`${SHIPPO_BASE}/transactions/`, {57 method: 'POST',58 headers: shippoHeaders,59 body,60 })61 const labelData = await labelRes.json()62 return new Response(JSON.stringify(labelData), {63 status: labelRes.status,64 headers: { ...corsHeaders, 'Content-Type': 'application/json' },65 })66 } else if (action === 'track') {67 const carrier = url.searchParams.get('carrier')68 const trackingNumber = url.searchParams.get('trackingNumber')69 const trackRes = await fetch(`${SHIPPO_BASE}/tracks/${carrier}/${trackingNumber}`, {70 headers: shippoHeaders,71 })72 const trackData = await trackRes.json()73 return new Response(JSON.stringify(trackData), {74 status: trackRes.status,75 headers: { ...corsHeaders, 'Content-Type': 'application/json' },76 })77 } else {78 return new Response(79 JSON.stringify({ error: 'Invalid action. Use: rates, label, or track' }),80 { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }81 )82 }83 } catch (error) {84 return new Response(85 JSON.stringify({ error: error.message }),86 { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }87 )88 }89})Pro tip: Always set 'async': false in Shippo Shipment creation requests to get rates synchronously. The default async behavior requires polling a separate endpoint for results, which complicates the Edge Function significantly.
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.
Build the rate comparison and label generation UI
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.
Build a shipping rate comparison component using the shippo-proxy Edge Function. Pre-fill the sender address with my warehouse details (I'll add them as constants). The form collects recipient name, address line 1, city, state, zip, and country code, plus package weight in ounces and dimensions in inches. On Get Rates click, call shippo-proxy with action=rates and the Shippo Shipment body. Display rate cards sorted by price: show carrier name, service level, estimated days, and price. Add a Select Rate button on each card. When a rate is selected, show a Confirm & Generate Label button. On confirm, call shippo-proxy with action=label and the selected rate's object_id. Show the returned label_url as a Download Label button and save the tracking number to the current order in Supabase.
Paste this in Lovable chat
Pro tip: Shippo returns both label_url (PNG format) and label_url_pdf for each transaction. Use label_url_pdf for print-quality labels that carriers accept. Display label_url as a preview thumbnail if needed.
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.
Common 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.
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.
Copy this prompt to try it in Lovable
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.
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.
Copy this prompt to try it in Lovable
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.
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.
Copy this prompt to try it in Lovable
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.
1// Correct Shippo Basic Auth construction2const authHeader = 'Basic ' + btoa(`${apiToken}:`) // Note the colon after token, empty passwordRate 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.
1// Correct address structure for Shippo2const address = {3 name: 'Jane Doe',4 street1: '123 Main St',5 city: 'New York',6 state: 'NY', // 2-letter state code for US7 zip: '10001',8 country: 'US', // ISO 2-letter, NOT 'USA'9 validate: true // Get detailed validation errors10}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.
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.
Alternatives
ShipStation is a better fit if you need a full shipping operations dashboard for high-volume order management, rather than embedding shipping as a feature within a custom app.
The UPS API directly is preferable if you exclusively ship UPS and want access to the full range of UPS account-specific rates and services without a third-party intermediary.
AfterShip is the better choice if your primary need is tracking across multiple carriers after label generation, with branded tracking pages and customer notifications rather than rate comparison.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation