Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

UPS API

Connect FlutterFlow to the UPS API using a Firebase Cloud Function as an OAuth 2.0 proxy. Because UPS uses client-credentials token exchange, your client secret must never ship inside the app. The Cloud Function holds the secret, mints and caches the Bearer token, then returns tracking or rate data to a standard FlutterFlow API Call — keeping your credentials safe on all platforms.

What you'll learn

  • Why UPS OAuth 2.0 client credentials require a Cloud Function proxy in FlutterFlow
  • How to write a Node.js Cloud Function that mints and caches a UPS Bearer token
  • How to configure a FlutterFlow API Group pointed at your Cloud Function
  • How to bind UPS tracking activity data to a timeline ListView
  • How to switch between UPS sandbox and production environments safely
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced15 min read60 minutesProductivityLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to the UPS API using a Firebase Cloud Function as an OAuth 2.0 proxy. Because UPS uses client-credentials token exchange, your client secret must never ship inside the app. The Cloud Function holds the secret, mints and caches the Bearer token, then returns tracking or rate data to a standard FlutterFlow API Call — keeping your credentials safe on all platforms.

Quick facts about this guide
FactValue
ToolUPS API
CategoryProductivity
MethodFlutterFlow API Call
DifficultyAdvanced
Time required60 minutes
Last updatedJuly 2026

Adding UPS Tracking and Rate Quotes to a FlutterFlow App

UPS's developer API has moved to OAuth 2.0 client credentials authentication. To get data, you first POST your client ID and secret to the UPS token endpoint, receive a Bearer token valid for roughly 14,400 seconds (four hours), and then pass that token in the Authorization header of every subsequent API call. This sounds straightforward, but it creates a fundamental problem for FlutterFlow: FlutterFlow compiles your app into native Flutter code that runs on the user's device. Any secret you embed in an API Call header or App Constant gets bundled into the app binary and can be extracted by anyone who downloads it.

The solution is a Firebase Cloud Function — a small piece of Node.js code that runs on Google's servers, never reaches the user's device, and is the only place your UPS client secret ever lives. The Cloud Function accepts a tracking number from your app, exchanges the client credentials for a Bearer token (caching it so you are not requesting a new one on every call), calls the UPS Tracking API, and returns just the tracking events as clean JSON. Your FlutterFlow API Group then points at your Cloud Function URL instead of directly at UPS servers.

A free developer account at developer.ups.com is all you need to start. UPS provides a sandbox environment for testing without generating real shipments or burning production rate limits. API access for production rating and label creation is tied to a UPS shipping account, but tracking works with just the developer credentials once approved.

Integration method

FlutterFlow API Call

UPS's modern API uses OAuth 2.0 client credentials — you POST a client ID and secret to get a Bearer token, then call tracking or rating endpoints. Because the client secret cannot be stored safely in a compiled Flutter app, the correct pattern is a Firebase Cloud Function that holds the secret, exchanges it for a token, caches the token for its 14,400-second lifetime, and returns only the data your app needs. Your FlutterFlow app never sees the UPS credentials at all.

Prerequisites

  • A UPS developer account at developer.ups.com (free sign-up)
  • A UPS application created in the developer portal to obtain your Client ID and Client Secret
  • A Firebase project with Cloud Functions enabled (Blaze pay-as-you-go plan required for outbound HTTP calls from Functions)
  • A FlutterFlow project with Firebase connected via Settings & Integrations
  • Basic familiarity with the FlutterFlow API Calls panel and how to bind responses to widgets

Step-by-step guide

1

Create a UPS developer app and note your credentials

Open developer.ups.com and sign in with your UPS account. Navigate to the Apps section and click 'Create App'. Give it a name (for example, 'FlutterFlow Tracker'), select the APIs you need — at minimum 'Tracking' for order status — and accept the terms. After creation, the portal displays your Client ID and Client Secret on the app detail screen. Copy both values and store them somewhere safe like a password manager; you will paste them into your Cloud Function's environment configuration and they must never appear in your FlutterFlow project. Note the two base URLs for UPS environments. For sandbox testing, use `https://wwwcie.ups.com` as the host. For production, use `https://onlinetools.ups.com`. Both environments use identical API paths — the only difference is the hostname. The token endpoint in both environments is `/security/v1/oauth/token`. The Tracking API path is `/api/track/v1/details/{trackingNumber}`. Setting a clear reminder to switch from sandbox to production before release is important: mixing environments causes 404 responses because sandbox tracking numbers don't exist in the production database and vice versa.

Pro tip: UPS provides a set of test tracking numbers in their developer documentation under 'Tracking API Test Data'. Use these sandbox numbers throughout development so you never accidentally query production data during testing.

Expected result: You have a Client ID and Client Secret from the UPS developer portal and know which base URL targets sandbox versus production.

2

Deploy a Firebase Cloud Function to proxy the OAuth token exchange

This step is the security core of the entire integration. The UPS client secret cannot safely live in a Flutter app because the compiled binary can be decompiled. It belongs exclusively in a Cloud Function environment variable on Google's servers. Open the Firebase console and navigate to your project. Go to Functions and create a new function named `upsTrack`. Set two environment variables under Edit Configuration: `UPS_CLIENT_ID` and `UPS_CLIENT_SECRET` — paste the values from the UPS developer portal here, not into any FlutterFlow field. The function below performs the full flow: it checks whether it already holds a valid cached token (UPS tokens last 14,400 seconds), requests a new one from UPS if needed, then calls the UPS Tracking API with the cached token and returns the package activity array to your FlutterFlow app. Your app only ever sends a tracking number to your Cloud Function URL — it never communicates directly with UPS and never holds any credentials. Note the CORS headers in the function. These are required for FlutterFlow web builds; native iOS and Android builds don't need them but including them keeps the same function usable across all targets without changes.

index.js
1// Firebase Cloud Function — index.js
2const functions = require('firebase-functions');
3const axios = require('axios');
4
5// In-memory token cache
6let cachedToken = null;
7let tokenExpiry = 0;
8
9const UPS_BASE = 'https://wwwcie.ups.com'; // change to onlinetools.ups.com for production
10
11async function getUpsToken() {
12 const now = Date.now();
13 if (cachedToken && now < tokenExpiry) {
14 return cachedToken;
15 }
16 const clientId = process.env.UPS_CLIENT_ID;
17 const clientSecret = process.env.UPS_CLIENT_SECRET;
18 const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
19
20 const response = await axios.post(
21 `${UPS_BASE}/security/v1/oauth/token`,
22 'grant_type=client_credentials',
23 {
24 headers: {
25 'Authorization': `Basic ${credentials}`,
26 'Content-Type': 'application/x-www-form-urlencoded',
27 'x-merchant-id': clientId
28 }
29 }
30 );
31
32 cachedToken = response.data.access_token;
33 // 400s safety buffer before the 14,400s UPS token expiry
34 tokenExpiry = now + 14000 * 1000;
35 return cachedToken;
36}
37
38exports.upsTrack = functions.https.onRequest(async (req, res) => {
39 res.set('Access-Control-Allow-Origin', '*');
40 if (req.method === 'OPTIONS') {
41 res.set('Access-Control-Allow-Methods', 'GET');
42 res.set('Access-Control-Allow-Headers', 'Content-Type');
43 return res.status(204).send('');
44 }
45
46 const trackingNumber = req.query.trackingNumber;
47 if (!trackingNumber) {
48 return res.status(400).json({ error: 'trackingNumber query param required' });
49 }
50
51 try {
52 const token = await getUpsToken();
53 const trackRes = await axios.get(
54 `${UPS_BASE}/api/track/v1/details/${trackingNumber}`,
55 {
56 headers: {
57 'Authorization': `Bearer ${token}`,
58 'transId': `ff-${Date.now()}`,
59 'transactionSrc': 'FlutterFlowApp'
60 }
61 }
62 );
63 const activity = trackRes.data
64 ?.trackResponse
65 ?.shipment?.[0]
66 ?.package?.[0]
67 ?.activity ?? [];
68 return res.json({ activity });
69 } catch (err) {
70 const status = err?.response?.status ?? 500;
71 return res.status(status).json({ error: err.message });
72 }
73});
74

Pro tip: The Cloud Function's in-memory token cache resets on cold starts. For high-traffic apps, consider persisting the token and expiry to Firestore so all function instances share a single cached token regardless of cold starts.

Expected result: Your Cloud Function is deployed at a URL like `https://us-central1-YOUR_PROJECT.cloudfunctions.net/upsTrack`. Testing it in a browser by appending `?trackingNumber=1Z...` returns a JSON object with an `activity` array.

3

Create a FlutterFlow API Group pointed at your Cloud Function

In FlutterFlow, click 'API Calls' in the left navigation panel. Click '+ Add' and select 'Create API Group'. Name it 'UPS Tracker'. In the Base URL field, enter the root of your Cloud Function URL — everything before the function name path. For example: `https://us-central1-YOUR_PROJECT.cloudfunctions.net`. You do not need to add Authorization headers to this API Group. Your FlutterFlow app is calling your own Firebase Cloud Function (a public HTTPS endpoint), which handles all UPS authentication internally. Keep this API Group header-free. Inside the UPS Tracker group, click '+ Add API Call'. Name it 'Track Package'. Set the method to GET and the endpoint path to `/upsTrack`. Switch to the Variables tab and add one variable: `trackingNumber` (type String). Back on the main tab, update the endpoint to `/upsTrack?trackingNumber={{trackingNumber}}`. Click 'Response & Test'. Enter a UPS sandbox test tracking number in the `trackingNumber` test field and run the call. You should see the JSON response with an `activity` array. Click 'Generate from Response' to have FlutterFlow automatically create JSON Paths like `$.activity[*].location.address.city`, `$.activity[*].description.descriptionCode`, `$.activity[*].date`, and `$.activity[*].time`. These paths are what you will bind to Text widgets in your ListView.

typescript
1// Reference — API Call configuration
2// Group name: UPS Tracker
3// Base URL: https://us-central1-YOUR_PROJECT.cloudfunctions.net
4// Call name: Track Package
5// Method: GET
6// Endpoint: /upsTrack?trackingNumber={{trackingNumber}}
7//
8// Useful JSON Paths from response:
9// $.activity[*].description.descriptionCode — event type
10// $.activity[*].location.address.city — scan city
11// $.activity[*].location.address.stateProvince
12// $.activity[*].date — YYYYMMDD format
13// $.activity[*].time — HHMMSS format
14

Pro tip: If the test call succeeds in FlutterFlow but shows empty JSON Paths, check that your Cloud Function is actually returning the `activity` key. An empty array `[]` is valid and will generate no paths — use a tracking number that has scan events.

Expected result: The 'Track Package' API Call appears under the UPS Tracker group. The Response tab shows the activity array and lists usable JSON Paths for binding to widgets.

4

Bind the tracking response to a timeline ListView

Add a new Page to your FlutterFlow project named 'Track Package'. At the top, place a TextField for the tracking number input and a Button labeled 'Track'. Below these, add a ListView widget. Select the ListView and open its Backend Query settings. Set the Data Source to 'API Call', select 'UPS Tracker → Track Package', and bind the `trackingNumber` argument to the TextField's value using Set from Variable. Inside the ListView item template, add a Column containing: a Text widget bound to `$.activity[*].description.descriptionCode` (the event name), a Text widget for location using `$.activity[*].location.address.city` combined with `$.activity[*].location.address.stateProvince`, and a Text widget for the date and time. UPS returns dates in YYYYMMDD format and times in HHMMSS format — you may want to add a Custom Function to reformat these into a more readable string like 'Jul 10, 2026 at 2:30 PM'. UPS returns events in reverse-chronological order (newest first), so the ListView naturally shows the most recent scan at the top without sorting. Add a Conditional Visibility rule on a 'No tracking data yet' Text widget that appears when the activity list is empty — this handles invalid tracking numbers and shipments that haven't been scanned yet. For the Track button's action, set it to Rebuild the ListView's Backend Query with the current TextField value rather than navigating away, so results appear inline on the same screen after the user taps Track.

Pro tip: UPS date and time fields are compact strings: date is YYYYMMDD and time is HHMMSS. A two-line Custom Function can reformat these: split date into year/month/day and time into hour/minute/second, then compose a human-readable string.

Expected result: Entering a sandbox tracking number and tapping Track populates the ListView with a timeline of scan events. Each row shows the event description, city, state, and formatted timestamp. An empty-state message appears for invalid tracking numbers.

5

Switch to production and prepare for release

When you are ready to release the app, switch the Cloud Function from the UPS sandbox to production. In your Cloud Function code (`index.js`), change the value of `UPS_BASE` from `https://wwwcie.ups.com` to `https://onlinetools.ups.com` and redeploy the function from the Firebase console. Nothing in your FlutterFlow project needs to change — your app still calls the same Cloud Function URL; only the function's internal upstream target changes. For production use, log in to developer.ups.com and follow the steps to promote your app from sandbox to production status. Depending on which UPS APIs you use, you may need to provide a UPS account number. Tracking-only integrations are generally approved quickly; rating and label creation require more account verification. For rate limit management in production: UPS does not publish a single per-key rate limit, but treat it conservatively. The token caching in your Cloud Function already eliminates redundant auth requests. Avoid triggering tracking refreshes automatically on page load — instead, fire the API call only when the user taps Track, and store the last result in App State so navigating back to the screen doesn't re-request. If you need to track many shipments in bulk, use a Firebase Scheduled Function to pre-fetch and cache statuses in Firestore rather than hitting UPS in real time from the mobile app.

index.js
1// Flip one constant in index.js to switch environments:
2// const UPS_BASE = 'https://wwwcie.ups.com'; // sandbox
3// const UPS_BASE = 'https://onlinetools.ups.com'; // production
4// Redeploy the function — no FlutterFlow changes needed.
5

Pro tip: If you'd rather skip building and maintaining the Cloud Function yourself, RapidDev's team builds FlutterFlow integrations like this every week — book a free scoping call at rapidevelopers.com/contact.

Expected result: The Cloud Function targets production UPS endpoints, real tracking numbers return live scan data, and no UPS credentials are accessible from the client app.

Common use cases

E-commerce order tracking screen

A mobile shop app lets customers enter a UPS tracking number and see a real-time timeline of scan events — shipped, in transit, out for delivery, delivered — pulled live from UPS's tracking API. The screen updates a status badge and shows the estimated delivery date from the most recent scan.

FlutterFlow Prompt

Build a package tracking screen where users enter a UPS tracking number and see a vertical timeline of scan events with location, timestamp, and status description for each step.

Copy this prompt to try it in FlutterFlow

Shipping rate calculator for a dropshipping app

A FlutterFlow app for dropshipping founders that lets them enter package dimensions, weight, origin ZIP, and destination ZIP, then displays rate options pulled from the UPS Rating API before confirming a shipment. Results show service name, transit days, and price for each option.

FlutterFlow Prompt

Add a rate-quote screen where I enter package weight, dimensions, origin ZIP, and destination ZIP, and the app shows me UPS service options with estimated prices and delivery times.

Copy this prompt to try it in FlutterFlow

Internal logistics dashboard for warehouse staff

A tablet app for warehouse teams shows a ListView of outbound shipments with their latest UPS tracking statuses. Staff tap any row to drill into the full scan history. The Cloud Function caches tokens to avoid rate limits even when many shipments are checked simultaneously.

FlutterFlow Prompt

Build a warehouse dashboard showing a list of UPS tracking numbers with each shipment's latest status. Tapping a row opens a detail screen with the full scan event timeline.

Copy this prompt to try it in FlutterFlow

Troubleshooting

Cloud Function returns 401 Unauthorized or an 'invalid_client' error

Cause: The UPS_CLIENT_ID or UPS_CLIENT_SECRET environment variable in Cloud Functions is incorrect, missing, or belongs to the wrong environment (sandbox credentials won't work against the production endpoint).

Solution: Open the Firebase console, go to Functions, click your upsTrack function, and verify the environment variables UPS_CLIENT_ID and UPS_CLIENT_SECRET match exactly what the UPS developer portal shows. Copy-paste rather than retyping to avoid invisible character errors. Also confirm the UPS_BASE URL in your function code matches the environment whose credentials you are using.

Published web app shows 'No Access-Control-Allow-Origin header is present' but Test Mode works

Cause: FlutterFlow's Test Mode proxies calls through FlutterFlow's servers, which masks CORS errors. The published web app runs in a real browser and enforces the same-origin policy, so missing CORS headers become visible.

Solution: Ensure the Cloud Function includes `res.set('Access-Control-Allow-Origin', '*')` at the very top of the request handler and returns a 204 response for OPTIONS preflight requests. The sample code in Step 2 includes this correctly. Redeploy the function after adding the headers.

typescript
1res.set('Access-Control-Allow-Origin', '*');
2if (req.method === 'OPTIONS') {
3 res.set('Access-Control-Allow-Methods', 'GET');
4 res.set('Access-Control-Allow-Headers', 'Content-Type');
5 return res.status(204).send('');
6}

Tracking API returns 404 or empty activity array for a real tracking number

Cause: The Cloud Function is still pointing at the UPS sandbox environment (`wwwcie.ups.com`) while you are testing with a real production tracking number, or vice versa. Sandbox tracking numbers only return data from the sandbox; they don't exist in production.

Solution: Use UPS's published sandbox test tracking numbers (available in their developer documentation) while UPS_BASE is set to `wwwcie.ups.com`. Switch to real tracking numbers only after changing UPS_BASE to `onlinetools.ups.com` and redeploying.

Each tracking request takes noticeably longer than the first call after a function restart

Cause: The Cloud Function cold-started and the in-memory token cache was reset, forcing a fresh token exchange on the first call. This adds 300-500ms to the first request after the function instance spins up.

Solution: For low-latency apps, set the Cloud Function's minimum instances to 1 in Firebase console to keep the function warm. For high-traffic apps, persist the token and its expiry timestamp to Firestore so all function instances share the cached token even after cold starts.

Best practices

  • Store UPS Client ID and Client Secret exclusively in Firebase Cloud Function environment variables — never in FlutterFlow App Constants, API Call headers, or Dart code.
  • Cache the UPS Bearer token in the Cloud Function for close to its full 14,400-second validity to avoid unnecessary token requests and reduce per-call latency.
  • Use a single constant (UPS_BASE) in the Cloud Function to switch between sandbox and production, so flipping environments requires one value change and a redeploy with no FlutterFlow edits.
  • Trigger tracking API calls only on explicit user action (tapping Track), not automatically on page load or scroll, to stay within UPS's rate limits on high-traffic apps.
  • Display a meaningful empty state when the activity array is empty — new shipments that haven't been scanned yet return an empty array, not an error.
  • Add error handling in your FlutterFlow Action Flow: check the API Call response code and show a Snackbar with a user-friendly message when the response is not 200.
  • For bulk tracking needs (a warehouse dashboard), use a Firebase Scheduled Function to pre-fetch statuses into Firestore and read from there in the app, rather than hitting UPS in real time per user action.

Alternatives

Frequently asked questions

Can I call the UPS API directly from FlutterFlow without a Cloud Function?

Technically possible for read-only flows if you obtain a token separately and paste it into App Constants, but strongly inadvisable. The OAuth client secret used to mint that token grants full API access including label printing and shipping charges. If it ends up in the compiled app binary, anyone who decompiles the APK or IPA can extract it and use your UPS account. The Cloud Function proxy is the correct pattern for any credential with write or billing access.

Does FlutterFlow Test Mode work with the Cloud Function integration?

Yes. FlutterFlow's Run Mode proxies API calls through FlutterFlow's servers, which means your Cloud Function endpoint and the JSON Path bindings work correctly in the browser preview. The Cloud Function is just an HTTPS URL, so Test Mode treats it the same as any other REST endpoint.

How do I test with realistic data before switching to production?

UPS provides a set of test tracking numbers in their developer portal under the Tracking API documentation. These numbers return realistic, predictable sets of scan events from the sandbox environment. Use them to validate your timeline ListView rendering and date-formatting logic before changing UPS_BASE to the production URL.

Does this integration work on both iOS and Android?

Yes. Because the integration uses a standard HTTPS API Call to your Cloud Function and has no native platform dependencies, it works identically on iOS, Android, and web builds. No platform permissions like location or camera are needed for package tracking. CORS only affects web builds, which is already handled by the headers in the Cloud Function.

What happens if UPS returns a 5xx error or times out?

The Cloud Function returns the upstream HTTP status code to FlutterFlow. Add error handling in your Action Flow: check whether the response code is 200, and if not, show a Snackbar with a message like 'Could not load tracking data — please try again'. This prevents the ListView from silently displaying nothing when UPS has a transient issue.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.