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

AfterShip

Connect FlutterFlow to AfterShip using the API Calls panel — create an API Group pointing to https://api.aftership.com/v4 with a static 'aftership-api-key' header, then call POST /trackings to register a shipment and GET /trackings/{slug}/{tracking_number} to read live status and checkpoints. No OAuth required. This is one of the most beginner-friendly REST integrations available in FlutterFlow.

What you'll learn

  • How to generate an AfterShip API key and understand the free plan's tracking limit
  • How to create an API Group in FlutterFlow with the aftership-api-key header
  • How to POST a new tracking to AfterShip and handle the 'tracking already exists' response
  • How to GET live shipment status and map the checkpoints array to a Data Type and timeline ListView
  • How to proxy the API key through a Firebase Cloud Function for secure production builds
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner17 min read30 minutesProductivityLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to AfterShip using the API Calls panel — create an API Group pointing to https://api.aftership.com/v4 with a static 'aftership-api-key' header, then call POST /trackings to register a shipment and GET /trackings/{slug}/{tracking_number} to read live status and checkpoints. No OAuth required. This is one of the most beginner-friendly REST integrations available in FlutterFlow.

Quick facts about this guide
FactValue
ToolAfterShip
CategoryProductivity
MethodFlutterFlow API Call
DifficultyBeginner
Time required30 minutes
Last updatedJuly 2026

Build a Package Tracking Screen with AfterShip in FlutterFlow

AfterShip tracks shipments across more than 1,100 carriers — UPS, FedEx, DHL, China Post, local last-mile carriers — using a single unified API. Instead of integrating each carrier separately, your FlutterFlow app calls one AfterShip endpoint and gets back structured checkpoint data regardless of which carrier is handling the package. This makes AfterShip the go-to solution for e-commerce apps, marketplace platforms, and any FlutterFlow project where customers need to follow their orders.

AfterShip's pricing tiers start with a free plan that includes approximately 50 trackings per month (verify current limits at aftership.com) and around 100 API calls. Paid plans start from approximately $11/month for higher volumes. The free plan is enough to build and test a fully functional tracking screen — the main gotcha is that the monthly tracking limit is per shipment registration, not per status refresh, so reusing a single tracking number while developing lets you call the status endpoint as many times as needed without burning quota.

The integration is genuinely beginner-friendly: one API Group, one header, two API Calls, and a handful of JSON Path mappings. You can have a working tracking screen in FlutterFlow in under 30 minutes. The only production consideration is moving the API key to a Firebase Cloud Function proxy so it does not ship inside the compiled app binary — but that optional step can wait until you are ready to release.

Integration method

FlutterFlow API Call

AfterShip's REST v4 API is secured with a single API key sent in the 'aftership-api-key' request header — no OAuth, no redirect flows, no token refresh. You define one API Group in FlutterFlow's API Calls panel, set the base URL and the header, then add two API Calls: a POST to register a tracking and a GET to read its current status and checkpoint history. JSON Path responses map directly into FlutterFlow Data Types that power a timeline ListView showing each delivery update.

Prerequisites

  • An AfterShip account (free plan sufficient for development) — sign up at app.aftership.com
  • Your AfterShip API key from the AfterShip dashboard (Settings → API Keys)
  • A real tracking number to test with (any recent parcel from a supported carrier)
  • A FlutterFlow project (any plan) with at least one screen created
  • Optional: a Firebase project with Cloud Functions enabled if you want to proxy the key for production releases

Step-by-step guide

1

Get your AfterShip API key

Open your browser and go to https://app.aftership.com. Log in or create a free account. Once inside the dashboard, navigate to Settings (gear icon in the left sidebar) → API Keys. Click 'Create API Key', give it a label like 'FlutterFlowApp', and copy the generated key to a secure place — your password manager or a private notes file. While you are in the AfterShip dashboard, take a moment to understand the free plan limits. The free tier includes approximately 50 shipment trackings per month (verify the current number at aftership.com/pricing, as limits change). Importantly, 'a tracking' means registering a new shipment — calling the GET status endpoint on an already-registered tracking does not consume additional quota. This means you can register one real tracking number, then call the status endpoint dozens of times during development without burning your monthly allowance. Also note AfterShip's carrier slug system. Carriers are identified by a short slug (e.g., 'usps' for United States Postal Service, 'fedex' for FedEx, 'dhl' for DHL). When you register a tracking, you can either pass the carrier slug explicitly or let AfterShip auto-detect it. Auto-detection works well for major carriers but can miss regional ones — passing the slug explicitly gives more reliable results.

Pro tip: If you need a carrier's slug, search the AfterShip courier list at aftership.com/couriers — they maintain a searchable directory of all 1,100+ supported carriers with their exact slug strings.

Expected result: You have an AfterShip API key copied, understand the free plan's monthly tracking limit, and have a real tracking number ready for testing.

2

Create the AfterShip API Group in FlutterFlow

Open your FlutterFlow project in the browser. Click API Calls in the left navigation panel (the plug/lightning icon). Click + Add → Create API Group. A dialog appears requesting a group name and base URL. Set: - Group name: AfterShip - Base URL: https://api.aftership.com/v4 Note: do not add a trailing slash after v4 — FlutterFlow concatenates the base URL with the endpoint path, and an extra slash can cause routing issues. Click the Headers tab. Add one header: - Header name: aftership-api-key - Value: your API key from Step 1 This is the only authentication AfterShip requires. The header name is lowercase and hyphenated — AfterShip also accepts 'as-api-key' as an alias, but 'aftership-api-key' is the primary name shown in their documentation. Do not add a 'Bearer ' prefix — this is a plain API key header, not a Bearer token. For web builds, note that AfterShip's CORS policy allows browser origins on most endpoints. You will likely not encounter CORS errors on web, but if you do (especially if testing from a localhost domain), the Firebase Cloud Function proxy approach in Step 6 resolves it. Native iOS and Android builds do not have CORS restrictions. Click Save. The API Group is now ready to hold your AfterShip API Calls.

api_group_config.txt
1// AfterShip API Group configuration reference:
2// Group name: AfterShip
3// Base URL: https://api.aftership.com/v4
4// Headers (static):
5// aftership-api-key: your-api-key-here
6//
7// Note: AfterShip also accepts 'as-api-key' as an alternate header name.

Pro tip: Keep the API key as a static header for now during development. If you later want different keys per environment (staging vs production), FlutterFlow's App Values or a Cloud Function proxy supports that pattern.

Expected result: An 'AfterShip' API Group appears in the API Calls panel with https://api.aftership.com/v4 as the base URL and aftership-api-key as a static header.

3

Add a POST /trackings call to register a shipment

Inside the AfterShip API Group, click + Add API Call. Name it 'Register Tracking'. Set the method to POST and the endpoint to trackings — the full URL becomes https://api.aftership.com/v4/trackings. Go to the Variables tab. Add two variables: - tracking_number (String, body) - slug (String, body) — this is the carrier slug (optional but recommended) In the Body tab, select JSON body type. Enter the request body structure. AfterShip wraps the tracking object in a 'tracking' key: { "tracking": { "tracking_number": "{{tracking_number}}", "slug": "{{slug}}" } } The slug field is optional — if you omit it, AfterShip will attempt auto-detection. For a better user experience in a real app, either let users select their carrier from a dropdown (mapped to AfterShip slugs) or use AfterShip's /couriers/detect endpoint to suggest the carrier based on the tracking number format. In the Response & Test tab, test the call with a real tracking number and carrier slug. A successful registration returns HTTP 201 Created with a JSON body containing the full tracking object including the AfterShip-assigned id. Map the response JSON path $.data.tracking.id into a variable if you need to reference the tracking later. Important edge case: if you call this endpoint with a tracking number that is already registered in your AfterShip account, you will get a 4009 error ('Tracking already exists'). This is not a fatal error — it means the tracking was already registered and you can proceed directly to the GET endpoint to read its status. Handle this in your Action Flow: treat 4009 as a success signal and skip to the status-reading step.

register_tracking_body.json
1// POST /trackings request body:
2{
3 "tracking": {
4 "tracking_number": "{{tracking_number}}",
5 "slug": "{{slug}}",
6 "title": "{{order_title}}"
7 }
8}
9// Response on success (201 Created):
10// $.data.tracking.id → AfterShip tracking ID
11// $.data.tracking.slug → resolved carrier slug
12// $.data.tracking.tag → current status tag (e.g. 'InTransit', 'Delivered')
13// $.data.tracking.checkpoints → array of checkpoint objects

Pro tip: Store the AfterShip tracking ID ($.data.tracking.id) in your Supabase or Firebase database alongside the order record so you can retrieve tracking status by ID later without requiring the user to re-enter the tracking number.

Expected result: The 'Register Tracking' API Call returns 201 Created with a populated JSON response when tested with a real tracking number. Error code 4009 ('Tracking already exists') is noted and handled as a non-fatal condition.

4

Add a GET /trackings call and map checkpoints to a Data Type

Inside the AfterShip API Group, add a second API Call. Name it 'Get Tracking Status'. Set the method to GET and the endpoint to trackings/{{slug}}/{{tracking_number}} — this is the AfterShip endpoint that returns full status and checkpoint history for a registered tracking. In the Variables tab, add two path variables: slug (String) and tracking_number (String). These are already defined by your API Group pattern from the previous step. In the Response & Test tab, test with a real slug and tracking number (one you already registered in Step 3). Paste the JSON response into the sample field. AfterShip returns a rich response including the current status tag, estimated delivery time, origin/destination country, and most importantly a checkpoints array. Create a Data Type called 'AfterShipCheckpoint' with these fields: - created_at (String) - tag (String) — checkpoint category (e.g. 'InTransit', 'OutForDelivery') - message (String) — human-readable status description - location (String) — city, state or facility name - country_name (String) Create a second Data Type called 'AfterShipTracking' with fields: - tracking_number (String) - slug (String) - tag (String) — overall current status - expected_delivery (String) - checkpoints (List of AfterShipCheckpoint) In the Response & Test panel, use Generate JSON Paths. Map: - $.data.tracking.tag → tag - $.data.tracking.expected_delivery → expected_delivery - $.data.tracking.checkpoints[*].message → checkpoints[].message - $.data.tracking.checkpoints[*].tag → checkpoints[].tag - $.data.tracking.checkpoints[*].created_at → checkpoints[].created_at - $.data.tracking.checkpoints[*].location → checkpoints[].location

json_paths.txt
1// JSON Paths for GET /trackings/{{slug}}/{{tracking_number}} response:
2// $.data.tracking.tag → current status (String)
3// $.data.tracking.expected_delivery → estimated delivery date (String)
4// $.data.tracking.origin_country_iso3 → origin country code (String)
5// $.data.tracking.destination_country_iso3 → destination country (String)
6// $.data.tracking.checkpoints[*].tag → checkpoint status tags (List<String>)
7// $.data.tracking.checkpoints[*].message → checkpoint messages (List<String>)
8// $.data.tracking.checkpoints[*].created_at → timestamps (List<String>)
9// $.data.tracking.checkpoints[*].location → locations (List<String>)

Pro tip: The checkpoints array is ordered newest-first by default in AfterShip responses. If you display them in a timeline ListView, the most recent status will appear at the top — which is usually what users expect when checking where their package is.

Expected result: The 'Get Tracking Status' API Call returns 200 OK with a populated checkpoints array. Data Types for AfterShipCheckpoint and AfterShipTracking are created in FlutterFlow with JSON Path bindings.

5

Build the tracking screen: form input, status badge, and checkpoint timeline

Now wire everything to the FlutterFlow UI. Create a tracking screen with two sections: the input area at the top and the timeline below. For the input area: add a TextField widget (hint text: 'Enter tracking number') and a Button labeled 'Track Package'. Select the Button → open the Actions panel → + Add Action → Backend/API Calls. Add the Register Tracking call first (with the tracking number from the TextField's controller), followed immediately by the Get Tracking Status call. In the Action Flow Editor, add branching: if the Register Tracking call returns code 4009 (tracking exists), skip it and proceed to Get Tracking Status directly. For the status badge: add a Container with a background color driven by a condition on the tracking's tag field. Common tag values from AfterShip: 'Pending' (grey), 'InTransit' (blue), 'OutForDelivery' (orange), 'Delivered' (green), 'Exception' (red). Use FlutterFlow's conditional styling to color the container based on the tag string. For the checkpoint timeline: add a ListView below the badge, backed by the checkpoints Data Type list from your Get Tracking Status response. Inside each list item, add: - A small dot or icon (use FlutterFlow's Icon widget with a circle) - A Text widget for the checkpoint message, bold - A Text widget for the location in a smaller font - A Text widget for the timestamp Store the checkpoints in App State (List type) after the API call and bind the ListView to that App State list. Add a CircularProgressIndicator (loading spinner) widget that shows while the API call is in progress, using FlutterFlow's conditional visibility tied to a boolean App State variable 'isLoading'. Test in FlutterFlow's Test Mode with a real tracking number. The timeline should populate with checkpoint history, and the status badge should reflect the current delivery state.

Pro tip: AfterShip's 'tag' field uses PascalCase values like 'InTransit' and 'OutForDelivery'. Use FlutterFlow's conditional expressions to convert these to user-friendly labels: 'InTransit' → 'In Transit', 'OutForDelivery' → 'Out for Delivery', etc.

Expected result: The tracking screen accepts a tracking number, calls AfterShip, and displays a colored status badge and a scrollable timeline of delivery checkpoints with locations and timestamps.

6

(Optional) Proxy the API key through Firebase for production builds

For any app you release to the App Store or Play Store, the API key should not be embedded in the compiled binary. While AfterShip API keys have more limited blast radius than payment keys (they grant read/track access, not financial operations), a leaked key can still exhaust your monthly tracking quota or expose your shipment data. The Firebase Cloud Function proxy pattern is the same as other REST integrations: a lightweight Node.js function that accepts the tracking slug and tracking number from FlutterFlow, adds the AfterShip API key from server-side config, calls AfterShip, and returns the response. In FlutterFlow, update your API Group's base URL to point to the Cloud Function URL instead of api.aftership.com. For the Register Tracking call (POST), the Cloud Function proxies the JSON body to AfterShip. For the Get Tracking Status call (GET), it forwards the path parameters. The Cloud Function also handles CORS, which is useful if you have a web build that encounters browser restrictions. If you want a simpler production option without Firebase, consider storing the API key in your Supabase project's Edge Function environment variables and creating a Supabase Edge Function as the proxy instead. Both patterns work — choose whichever backend your FlutterFlow app is already using for other features. If you need help setting this up, RapidDev's team handles FlutterFlow API integrations like this regularly and offers a free consultation at rapidevelopers.com/contact.

index.js
1// Firebase Cloud Function — AfterShip proxy (Node.js, index.js)
2const functions = require('firebase-functions');
3const axios = require('axios');
4
5exports.aftershipProxy = functions.https.onRequest(async (req, res) => {
6 res.set('Access-Control-Allow-Origin', '*');
7 if (req.method === 'OPTIONS') {
8 res.set('Access-Control-Allow-Methods', 'GET, POST');
9 res.set('Access-Control-Allow-Headers', 'Content-Type');
10 res.status(204).send('');
11 return;
12 }
13
14 const apiKey = functions.config().aftership.apikey;
15 const path = req.query.path || 'trackings';
16
17 try {
18 const response = await axios({
19 method: req.method,
20 url: `https://api.aftership.com/v4/${path}`,
21 headers: {
22 'aftership-api-key': apiKey,
23 'Content-Type': 'application/json'
24 },
25 data: req.method === 'POST' ? req.body : undefined
26 });
27 res.status(response.status).json(response.data);
28 } catch (error) {
29 const status = error.response ? error.response.status : 500;
30 res.status(status).json(error.response ? error.response.data : { error: 'Proxy error' });
31 }
32});

Pro tip: Set the AfterShip key with firebase functions:config:set aftership.apikey='your-key-here'. The Cloud Function reads it from functions.config().aftership.apikey — it never appears in the source file.

Expected result: FlutterFlow API Calls target the Cloud Function URL. The AfterShip API key is stored server-side in Firebase config. The compiled Flutter app contains no AfterShip credentials.

Common use cases

Order tracking screen for an e-commerce app

An e-commerce mobile app built in FlutterFlow adds an 'Track My Order' screen. When a customer enters their tracking number, the app calls AfterShip's GET endpoint and displays a timeline of checkpoints — 'Picked up', 'In transit', 'Out for delivery', 'Delivered' — with timestamps and carrier-specific status messages. The screen auto-refreshes every few minutes while the order is in transit.

FlutterFlow Prompt

Build a package tracking screen in FlutterFlow where the user can enter a tracking number, tap 'Track', and see a scrollable timeline of delivery checkpoints from AfterShip with the current status highlighted at the top.

Copy this prompt to try it in FlutterFlow

Shipment dashboard for a dropshipping store

A Shopify dropshipping store owner builds a FlutterFlow internal tablet app to monitor all active orders. The app shows a list of tracking numbers, each with their current AfterShip status badge (pending, in transit, delivered, exception). Tapping an order opens the full checkpoint detail view. The AfterShip free tier's 50-tracking limit is supplemented with a paid plan as order volume grows.

FlutterFlow Prompt

Build a dashboard screen listing multiple tracking numbers with their current AfterShip status as colored badges, and a detail view showing full checkpoint history when tapped.

Copy this prompt to try it in FlutterFlow

Post-purchase tracking link in a service app

A meal-kit or subscription box company builds a FlutterFlow iOS app that includes a 'Your Delivery' section. After fulfillment, the app stores the tracking number in its Supabase database. The tracking screen reads that number and calls AfterShip to show estimated delivery time and current location — reducing customer support tickets about order status.

FlutterFlow Prompt

Build a 'Your Delivery' screen that reads a saved tracking number from the logged-in user's profile and displays the current AfterShip status with estimated delivery date and latest checkpoint.

Copy this prompt to try it in FlutterFlow

Troubleshooting

POST /trackings returns error code 4009 'Tracking already exists'

Cause: The tracking number was already registered in your AfterShip account. This is not a true error — it means the tracking exists and you can read its status immediately.

Solution: In your FlutterFlow Action Flow, add a condition after the Register Tracking call: if the response code is 4009, skip to the Get Tracking Status call directly (do not show an error to the user). The tracking is already in AfterShip and will return live status. Only show an error to the user for unexpected status codes.

401 Unauthorized on every API call

Cause: The 'aftership-api-key' header is missing, has a typo in the header name, or the API key value is incorrect or has been regenerated.

Solution: Open the AfterShip API Group in FlutterFlow's API Calls panel and verify the header name is exactly 'aftership-api-key' (lowercase, hyphenated). Check the API key value in AfterShip's dashboard under Settings → API Keys. If the key was regenerated, update it in the API Group header. Note: the header name is NOT 'Authorization' — it is the AfterShip-specific header name.

Checkpoints ListView shows no data even though the API test returns checkpoints

Cause: The JSON Path mapping for the nested checkpoints array is incorrect, or the Data Type binding was set up on the wrong response field.

Solution: Open the Get Tracking Status API Call in the test panel. The checkpoints are at $.data.tracking.checkpoints[*].message — not $.checkpoints[*].message. The response has a 'data' wrapper object containing the 'tracking' object. Click Generate JSON Paths after pasting a real response to auto-generate the correct paths, then re-map the Data Type fields.

Free-tier API calls stop working mid-month

Cause: The AfterShip free plan's monthly tracking registration limit (approximately 50) was exhausted. All GET status calls still work for already-registered trackings, but registering new ones fails.

Solution: Check your AfterShip dashboard's usage section to see how many trackings have been registered this month. During development, reuse the same tracking number — register it once, then call GET status as many times as needed. To continue registering new trackings, upgrade to a paid AfterShip plan or wait for the monthly counter to reset.

Best practices

  • Reuse a single tracking number during development to avoid exhausting your free-plan monthly tracking quota — POST to register it once, then call GET status for testing.
  • Always handle the 4009 'Tracking already exists' error code as a non-fatal condition in your Action Flow and proceed to GET status instead of showing an error to users.
  • Pass the carrier slug explicitly when known rather than relying on AfterShip auto-detection — it produces more reliable results for regional and last-mile carriers.
  • Display AfterShip's 'tag' values as human-friendly labels in your UI ('InTransit' → 'In Transit', 'OutForDelivery' → 'Out for Delivery') using conditional text substitution in FlutterFlow.
  • For production releases, move the API key to a Firebase Cloud Function or Supabase Edge Function so it does not ship inside the compiled iOS/Android app binary.
  • Add an empty state to the checkpoints ListView for when AfterShip returns no checkpoint history yet — new trackings in 'Pending' status often have an empty checkpoints array.
  • Cache GET tracking responses in App State for 5-10 minutes to avoid redundant API calls if the user reopens the tracking screen frequently — delivery checkpoints rarely update more than once per hour.

Alternatives

Frequently asked questions

How many carriers does AfterShip support, and does it cover my carrier?

AfterShip supports more than 1,100 carriers worldwide as of early 2026, including all major international carriers (UPS, FedEx, DHL, USPS), regional carriers, and last-mile delivery services. Check aftership.com/couriers for the full searchable list with exact carrier slugs. If your carrier is not listed, AfterShip has a request process for adding new ones.

Does the AfterShip free plan include API access?

Yes, the AfterShip free plan includes API access with approximately 50 tracking registrations per month and around 100 API calls. These limits are sufficient for building and testing a FlutterFlow integration. Verify the current free plan limits at aftership.com/pricing, as they change occasionally. Paid plans start from approximately $11/month for higher volumes.

Can I use AfterShip to send tracking notifications to my app users?

AfterShip supports push notifications and email/SMS alerts through its separate Notifications product, but these are not directly integrated with FlutterFlow. To trigger push notifications in your FlutterFlow app when a tracking status changes, set up an AfterShip webhook that POSTs to a Firebase Cloud Function, which then sends a notification via Firebase Cloud Messaging. This requires a paid AfterShip plan with webhook support.

What is the carrier 'slug' and how do I find the right one?

The slug is AfterShip's identifier for each carrier — a short lowercase hyphenated string like 'usps', 'fedex', 'dhl', or 'ups'. It is a required or optional parameter when registering a tracking. Find the correct slug for any carrier at aftership.com/couriers in the searchable directory. Passing the wrong slug causes AfterShip to look up the tracking number against the wrong carrier, often returning no results.

Does AfterShip work for FlutterFlow web builds, or only mobile?

AfterShip's API generally allows browser origins, so web builds should work without CORS errors in most cases. If you encounter 'XMLHttpRequest error' on a web build, it means AfterShip is blocking your specific origin — the Firebase Cloud Function proxy in Step 6 resolves this by making the call server-side, eliminating the browser CORS restriction entirely.

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.