Connect ShipStation to your V0-generated Next.js app by creating a server-side API route that calls the ShipStation REST API. Store your ShipStation API key and secret in Vercel environment variables, then use the route handler to list orders, create shipping labels, and track packages. Your V0 frontend can display a full order fulfillment dashboard without exposing credentials to the browser.
Building an Order Fulfillment Dashboard with ShipStation and V0
ShipStation is the backbone of order fulfillment for thousands of e-commerce businesses, connecting Shopify, WooCommerce, Amazon, and other sales channels to carriers like USPS, UPS, FedEx, and DHL. If you are building an operations dashboard with V0, integrating ShipStation lets your team see all pending orders, create shipping labels in bulk, and track packages — all from a single custom interface tailored to your workflow.
V0 handles the UI generation: tables, filters, label printing buttons, status badges, and tracking timelines. The integration work happens in a Next.js API route that calls ShipStation's REST API. ShipStation uses HTTP Basic Auth where the username is your API key and the password is your API secret — both available in your ShipStation account settings. Because these credentials grant full access to your shipping account, they must stay server-side at all times.
This tutorial covers building a multi-tab fulfillment dashboard: an orders queue, a label creation form, and a shipment tracking panel. You will use ShipStation's API to list awaiting-shipment orders, create a shipment record, purchase a label, and mark orders as shipped — the complete fulfillment workflow in a custom V0 interface.
Integration method
ShipStation provides a RESTful API that uses HTTP Basic Auth with your API key and API secret. Integration happens through a Next.js route handler that authenticates with ShipStation's API, fetches orders, creates shipments and labels, and returns structured data to your V0 frontend. The API credentials are stored in Vercel environment variables so they are never exposed to the browser.
Prerequisites
- A V0 account at v0.dev with a Next.js project created
- A ShipStation account — sign up at shipstation.com (free 30-day trial available)
- Your ShipStation API key and API secret from Account Settings → API Settings
- A Vercel account connected to your V0 project for deployment
- At least one store and carrier connected to your ShipStation account
Step-by-step guide
Generate the Fulfillment Dashboard UI with V0
Generate the Fulfillment Dashboard UI with V0
Open V0 and describe the fulfillment dashboard you want to build. A good starting point is an orders table with status badges and action buttons. V0 will generate a React component using Tailwind CSS with a responsive table layout, filter controls, and appropriate loading and empty states. Request specific columns relevant to fulfillment: order number, customer name, items count, destination, order date, and status. Ask V0 to include a detail panel that expands when you click a row, showing line items, weight, and a label creation form. Make sure V0 generates proper TypeScript interfaces for the order data shape so the component is fully typed. The dashboard should handle pagination for large order lists and show a skeleton loading state while data is being fetched from ShipStation. Review the generated component in the V0 preview to ensure the layout matches your operational needs before wiring up the backend.
Create an order fulfillment dashboard with a table showing columns: Order #, Customer, Items, Ship To (state), Order Date, and Status (badge: awaiting shipment / shipped / cancelled). Add a search bar and status filter dropdown at the top. Each row should have a 'Ship' button. Include a total orders count. Show skeleton rows while loading. Fetch data from /api/shipstation/orders.
Paste this in V0 chat
Pro tip: Ask V0 to generate TypeScript interfaces for the ShipStation order shape early — having typed data models makes the API route and frontend integration much smoother.
Expected result: A styled fulfillment dashboard with a data table, filter controls, and action buttons renders in the V0 preview.
Create the ShipStation Orders API Route
Create the ShipStation Orders API Route
Create a Next.js App Router route handler at app/api/shipstation/orders/route.ts. The ShipStation API uses HTTP Basic Auth where the username is your API key and the password is your API secret. Encode these as a Base64 Basic Auth header using Node's built-in Buffer. The GET handler should accept query parameters for filtering: orderStatus (awaiting_shipment, shipped, cancelled), pageSize, and page for pagination. Call the ShipStation Orders endpoint at https://ssapi.shipstation.com/orders with the appropriate query string. ShipStation returns a paginated response with an orders array, total count, and page information. Parse the response and return a cleaned-up JSON payload to your frontend with just the fields you need for the dashboard table. Add proper error handling for 401 (invalid credentials), 429 (rate limit — ShipStation allows 40 requests per minute on the standard plan), and network errors. Include rate limit headers in your error response so the frontend can implement retry logic.
1// app/api/shipstation/orders/route.ts2import { NextRequest, NextResponse } from 'next/server';34const SHIPSTATION_BASE = 'https://ssapi.shipstation.com';56function getAuthHeader() {7 const credentials = `${process.env.SHIPSTATION_API_KEY}:${process.env.SHIPSTATION_API_SECRET}`;8 return `Basic ${Buffer.from(credentials).toString('base64')}`;9}1011export async function GET(request: NextRequest) {12 const { searchParams } = new URL(request.url);13 const orderStatus = searchParams.get('orderStatus') || 'awaiting_shipment';14 const pageSize = searchParams.get('pageSize') || '50';15 const page = searchParams.get('page') || '1';1617 try {18 const url = new URL(`${SHIPSTATION_BASE}/orders`);19 url.searchParams.set('orderStatus', orderStatus);20 url.searchParams.set('pageSize', pageSize);21 url.searchParams.set('page', page);2223 const response = await fetch(url.toString(), {24 headers: {25 'Authorization': getAuthHeader(),26 'Content-Type': 'application/json',27 },28 // Cache for 30 seconds to reduce API calls29 next: { revalidate: 30 },30 });3132 if (response.status === 429) {33 return NextResponse.json(34 { error: 'Rate limit exceeded. Please wait before retrying.' },35 { status: 429 }36 );37 }3839 if (!response.ok) {40 return NextResponse.json(41 { error: `ShipStation API error: ${response.status}` },42 { status: response.status }43 );44 }4546 const data = await response.json();4748 return NextResponse.json({49 orders: data.orders,50 total: data.total,51 page: data.page,52 pages: data.pages,53 });54 } catch (error) {55 console.error('ShipStation orders error:', error);56 return NextResponse.json({ error: 'Failed to fetch orders' }, { status: 500 });57 }58}Pro tip: ShipStation's API rate limit is 40 requests per minute on most plans. Use Next.js route handler caching with next: { revalidate: 30 } to reduce repeated calls.
Expected result: Calling /api/shipstation/orders returns a JSON list of orders from ShipStation.
Create the Label Generation API Route
Create the Label Generation API Route
Create a route handler at app/api/shipstation/label/route.ts that creates a shipment and purchases a shipping label. This route accepts a POST request with the orderId and optional carrier/service selection. The ShipStation label creation flow has two steps: first create a shipment record from the order (POST /shipments/createlabel), then optionally void and recreate if the wrong carrier was selected. For simplicity, use the createLabelForOrder shortcut endpoint which creates the shipment and purchases the label in one call if you already know the carrier service code. The response includes a labelData property containing a Base64-encoded PDF of the label, which your frontend can decode and open in a new browser tab for printing. Also return the trackingNumber and carrierCode from the response so you can display them in the dashboard and mark the order as shipped. Add the shipmentId to your database so you can reference it later for tracking or voiding if needed.
Update the Ship button in the order table to open a modal with carrier/service selection dropdowns (USPS Priority, UPS Ground, FedEx Express), a weight field pre-filled from the order, and a 'Purchase Label' button. Show a loading spinner while the label is being created. On success, display the tracking number and a 'Print Label' button that opens a new tab with the PDF.
Paste this in V0 chat
1// app/api/shipstation/label/route.ts2import { NextRequest, NextResponse } from 'next/server';34function getAuthHeader() {5 const credentials = `${process.env.SHIPSTATION_API_KEY}:${process.env.SHIPSTATION_API_SECRET}`;6 return `Basic ${Buffer.from(credentials).toString('base64')}`;7}89export async function POST(request: NextRequest) {10 try {11 const body = await request.json();12 const { orderId, carrierCode, serviceCode, packageCode, weight } = body;1314 if (!orderId) {15 return NextResponse.json({ error: 'orderId is required' }, { status: 400 });16 }1718 const labelPayload = {19 orderId,20 carrierCode: carrierCode || 'stamps_com',21 serviceCode: serviceCode || 'usps_priority_mail',22 packageCode: packageCode || 'package',23 confirmation: 'none',24 shipDate: new Date().toISOString().split('T')[0],25 weight: weight || { value: 1, units: 'pounds' },26 testLabel: process.env.SHIPSTATION_TEST_MODE === 'true',27 };2829 const response = await fetch(30 'https://ssapi.shipstation.com/orders/createlabelfororder',31 {32 method: 'POST',33 headers: {34 'Authorization': getAuthHeader(),35 'Content-Type': 'application/json',36 },37 body: JSON.stringify(labelPayload),38 }39 );4041 const result = await response.json();4243 if (!response.ok) {44 return NextResponse.json(45 { error: result.ExceptionMessage || 'Label creation failed' },46 { status: response.status }47 );48 }4950 return NextResponse.json({51 shipmentId: result.shipmentId,52 trackingNumber: result.trackingNumber,53 carrierCode: result.carrierCode,54 serviceCode: result.serviceCode,55 labelData: result.labelData, // Base64 PDF56 });57 } catch (error) {58 console.error('ShipStation label error:', error);59 return NextResponse.json({ error: 'Label creation failed' }, { status: 500 });60 }61}Pro tip: Set SHIPSTATION_TEST_MODE=true in your development environment variables to generate test labels that look real but do not cost postage.
Expected result: Posting to /api/shipstation/label returns a JSON object with trackingNumber and labelData (Base64 PDF).
Add Vercel Environment Variables
Add Vercel Environment Variables
In the Vercel Dashboard, navigate to your project and click Settings → Environment Variables. Add SHIPSTATION_API_KEY and SHIPSTATION_API_SECRET — both found in your ShipStation account under Account Settings → API Settings. Add SHIPSTATION_TEST_MODE and set it to 'true' for Preview environments and 'false' for Production. These variables must not have the NEXT_PUBLIC_ prefix since they are used only in server-side route handlers. Set the variables for Production, Preview, and Development scopes. After adding variables, trigger a new deployment from the Vercel Dashboard or by pushing a commit to your connected GitHub repository. ShipStation API keys are account-level credentials giving full access to your orders, labels, and shipments, so treat them with the same care as a bank password. Rotate your API key immediately in ShipStation's Account Settings if you believe it has been exposed, and generate a new one to replace it in Vercel.
Pro tip: You can verify your ShipStation API credentials are working by hitting /api/shipstation/orders in your browser after deploying — a 200 response with an orders array confirms the credentials are correct.
Expected result: The Vercel Dashboard shows SHIPSTATION_API_KEY and SHIPSTATION_API_SECRET configured for all environment scopes.
Set Up ShipStation Webhooks for Real-Time Updates
Set Up ShipStation Webhooks for Real-Time Updates
ShipStation can push order and shipment status updates to your application via webhooks, eliminating the need to poll the API. Create a webhook receiver route at app/api/shipstation/webhook/route.ts that handles POST requests from ShipStation. In your ShipStation account, go to Account Settings → Webhooks and register your Vercel deployment URL plus the /api/shipstation/webhook path. ShipStation webhooks send a resourceUrl in the POST body that points to the updated resource — you fetch that URL with your API credentials to get the full updated record. Events include ORDER_NOTIFY (new order), SHIP_NOTIFY (label created), and FULFILLMENT_SHIPPED (marked shipped). Because ShipStation webhooks do not include a signature header for verification, protect your webhook endpoint by keeping the URL secret and optionally adding a custom query parameter token that you verify in the route handler. Update your database or frontend state whenever a webhook arrives so the fulfillment dashboard stays current without page refresh.
Add a real-time status indicator to the order table that shows a green pulsing dot when ShipStation has just updated an order's status. The component should poll /api/shipstation/orders every 60 seconds and highlight newly changed rows with a brief yellow flash animation.
Paste this in V0 chat
1// app/api/shipstation/webhook/route.ts2import { NextRequest, NextResponse } from 'next/server';34export async function POST(request: NextRequest) {5 try {6 const webhookToken = request.nextUrl.searchParams.get('token');7 if (webhookToken !== process.env.SHIPSTATION_WEBHOOK_TOKEN) {8 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });9 }1011 const body = await request.json();12 const { resource_url, resource_type } = body;1314 // Fetch the actual updated resource from ShipStation15 const credentials = `${process.env.SHIPSTATION_API_KEY}:${process.env.SHIPSTATION_API_SECRET}`;16 const authHeader = `Basic ${Buffer.from(credentials).toString('base64')}`;1718 const resourceResponse = await fetch(resource_url, {19 headers: { 'Authorization': authHeader },20 });21 const resourceData = await resourceResponse.json();2223 // TODO: Update your database with the new order/shipment status24 console.log(`ShipStation ${resource_type} update:`, resourceData);2526 return NextResponse.json({ received: true });27 } catch (error) {28 console.error('ShipStation webhook error:', error);29 return NextResponse.json({ error: 'Webhook processing failed' }, { status: 500 });30 }31}Pro tip: Register your webhook URL in ShipStation with a secret token as a query parameter (e.g., /api/shipstation/webhook?token=your-secret) and store that token in SHIPSTATION_WEBHOOK_TOKEN in Vercel.
Expected result: ShipStation sends a test webhook event and your route handler returns 200 with received: true in the response.
Common use cases
Orders Fulfillment Queue Dashboard
An e-commerce operations team uses V0 to build an internal dashboard showing all ShipStation orders in 'awaiting shipment' status, with filters by store and priority, and a one-click 'Create Label' button per order.
Build an order fulfillment dashboard with a data table showing order number, customer name, items ordered, destination state, and order date. Include filter dropdowns for order status and store name. Add a 'Create Label' button in each row that posts to /api/shipstation/label. Show a badge with carrier and tracking number after label creation.
Copy this prompt to try it in V0
Multi-Carrier Rate Comparison
A shipping manager needs to compare rates from USPS, UPS, and FedEx for each order before buying a label. The V0 UI fetches rates from ShipStation and displays them side-by-side so the cheapest option can be selected.
Create a rate comparison panel that accepts package weight and destination zip code, then shows a comparison table with carrier name, service level, delivery time estimate, and price. Highlight the cheapest option in green. Include a 'Buy Label' button for each rate that calls /api/shipstation/label/create.
Copy this prompt to try it in V0
Shipment Tracking Page
A customer-facing tracking page built with V0 lets customers enter their order number and see real-time shipment status fetched from ShipStation, including the carrier tracking link.
Build a package tracking page with a search input for order number, a timeline component showing order placed, label created, picked up, in transit, and delivered steps with timestamps, and a link to the carrier's tracking page. Fetch data from /api/shipstation/tracking?orderNumber=ORD-123.
Copy this prompt to try it in V0
Troubleshooting
401 Unauthorized from ShipStation API
Cause: The SHIPSTATION_API_KEY or SHIPSTATION_API_SECRET environment variable is incorrect, missing, or has not been applied to the current deployment.
Solution: Verify the API key and secret in ShipStation under Account Settings → API Settings. Make sure both variables are set in the correct Vercel environment scope (Production vs Preview) and that you have redeployed after making changes.
429 Too Many Requests errors from ShipStation
Cause: ShipStation enforces a rate limit of 40 API calls per minute on most plans. A dashboard that polls the orders endpoint frequently will hit this limit.
Solution: Add Next.js route handler caching with next: { revalidate: 30 } to the orders route to reduce API calls. Implement client-side polling with a 60-second interval rather than fetching on every render. Consider using ShipStation webhooks for real-time updates instead of polling.
1// Add to your fetch call inside the route handler2next: { revalidate: 30 }Label creation fails with 'Invalid carrier/service combination'
Cause: The carrierCode and serviceCode combination you specified is not available for the package's origin or destination, or the carrier is not connected to your ShipStation account.
Solution: First call the ShipStation /carriers/listservices?carrierCode=usps endpoint to get the list of valid services for each carrier, then use only valid combinations. Make sure the carrier is enabled in your ShipStation account under Account Settings → Shipping.
Webhook events are not being received
Cause: The webhook URL registered in ShipStation is not publicly accessible, or the Vercel deployment URL changed after the webhook was registered.
Solution: Verify the webhook URL in ShipStation Account Settings → Webhooks points to your current Vercel production URL. Use ShipStation's 'Send Test' feature to trigger a test event and check your Vercel Function logs for the incoming request.
Best practices
- Store SHIPSTATION_API_KEY and SHIPSTATION_API_SECRET in Vercel environment variables without the NEXT_PUBLIC_ prefix
- Use ShipStation webhooks for real-time order updates instead of polling the API on every page load
- Implement Next.js route handler caching to stay within ShipStation's 40 requests per minute rate limit
- Set SHIPSTATION_TEST_MODE=true in Preview environments to generate test labels without incurring postage costs
- Always return trackingNumber and carrierCode from your label creation route and store them in your database for reconciliation
- Protect webhook endpoints with a secret token query parameter since ShipStation does not provide request signatures
- Log all label creation and shipment events with timestamps to create an audit trail for customer service
Alternatives
AfterShip is better for tracking-only use cases across 1,100+ carriers without the label creation and order management features of ShipStation.
Shippo offers a similar multi-carrier label API with a more developer-friendly SDK and pay-per-label pricing, making it a good alternative for lower volume shippers.
The FedEx API is the right choice if you ship exclusively with FedEx and need direct carrier integration without a middleware platform.
Frequently asked questions
Where do I find my ShipStation API key and secret?
Log into ShipStation, click your account name in the top-right corner, then go to Account Settings → API Settings. Your API key and API secret are displayed there. Click 'Regenerate API Keys' if you need to create new credentials, but note this invalidates the old ones immediately.
Can I connect multiple ShipStation stores to one V0 app?
ShipStation supports multiple stores under one account, and the API returns a storeId field with each order. You can filter orders by storeId in your API route to build a multi-store dashboard. Use a single set of API credentials — they grant access to all stores on your account.
Does ShipStation support international shipping?
Yes, ShipStation supports international shipments for carriers that offer international services. Creating international labels requires customs declaration fields including harmonized codes, item descriptions, and declared values. Add these additional fields to your label creation API route for international orders.
How do I void a label I created by mistake?
Call the ShipStation void shipment endpoint at POST /shipments/voidlabel with the shipmentId. Add a DELETE handler to your API route or a separate /api/shipstation/label/void route to handle void requests from your V0 frontend. Labels must be voided before the carrier scans them.
What is the ShipStation rate limit and how do I avoid hitting it?
ShipStation allows 40 API requests per minute on standard plans. Use Next.js route handler caching (next: { revalidate: 30 }) on read-only endpoints, implement client-side polling at 60-second intervals rather than on every component render, and switch to webhooks for real-time updates to keep your API usage well within limits.
Can V0 generate the carrier rate comparison UI?
Yes. Prompt V0 to create a carrier rate comparison table with columns for carrier name, service level, estimated delivery days, and price in dollars. V0 will generate the table component with proper sorting and a highlight for the cheapest option. Wire it up to a /api/shipstation/rates route that calls the ShipStation /shipments/getrates endpoint.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation