# How to Integrate Bolt.new with ShipStation

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 30 minutes
- Last updated: April 2026

## TL;DR

Connect Bolt.new to ShipStation using its REST API with HTTP Basic authentication — just your API key and secret encoded as Base64. Build an API route that fetches orders, creates shipping labels, and shops rates across USPS, UPS, and FedEx. Outbound API calls work in Bolt's WebContainer during development. Webhook notifications for shipment status changes require a deployed URL on Netlify or Vercel.

## Build a Shipping Management Dashboard with ShipStation and Bolt.new

ShipStation sits at the center of e-commerce fulfillment — it aggregates orders from Shopify, WooCommerce, Amazon, Etsy, and dozens of other channels, lets you rate-shop across carriers in real time, and generates shipping labels with a single API call. Integrating it into a Bolt.new app lets you build custom shipping dashboards, automate label creation workflows, or surface shipping data inside your own branded admin panel without forcing your team to log into another third-party tool.

The ShipStation REST API uses HTTP Basic authentication, which is one of the simplest auth patterns available: you Base64-encode your API key and secret separated by a colon, then send that as the Authorization header. There is no OAuth flow to manage, no token refresh cycle, and no expiry. This simplicity makes it an excellent API to learn with and easy to debug. All credentials must be stored server-side in your .env file and accessed only from Next.js API routes — never from client-side React components.

During Bolt.new development, outbound calls to ShipStation's API endpoints work immediately in the WebContainer. You can fetch orders, shop rates, and test label creation from the preview environment. The only functionality that requires deployment is ShipStation's webhook system, which sends POST requests to your endpoint when order or shipment status changes occur. Since WebContainers cannot receive incoming HTTP connections, you must deploy to Netlify or Vercel and register your webhook URL in ShipStation's settings before webhook events will arrive.

## Before you start

- A ShipStation account with at least one connected carrier (USPS, UPS, or FedEx)
- A ShipStation API key and secret from Account → Account Settings → API Settings → Generate API Keys
- A Bolt.new account with a new Next.js project open
- Products or orders already in ShipStation (or a plan to create them via API) to test against
- A Netlify account for deployment if you need webhook notifications for shipment status changes

## Step-by-step guide

### 1. Get your ShipStation API credentials and set up authentication

ShipStation uses HTTP Basic authentication — one of the simplest auth schemes in any REST API ecosystem. You need two values: an API key and an API secret, both available from your ShipStation account settings.

To generate your credentials: log into ShipStation, click your account name in the top-right corner, then go to Account Settings → API Settings. You will see a section called API Keys. Click Generate API Keys. ShipStation immediately shows you both the API Key (a long alphanumeric string) and the API Secret. Copy both — the secret is shown once. Store them somewhere secure like a password manager before adding them to your project.

HTTP Basic authentication requires you to Base64-encode the combination `apiKey:apiSecret` and include it in the Authorization header as `Basic <encoded-string>`. In Node.js, this is straightforward: `Buffer.from('key:secret').toString('base64')`. Alternatively, you can use the native `fetch` without manual encoding by passing the credentials directly in the URL as `https://key:secret@ssapi.shipstation.com/...` — but the explicit header approach is cleaner and easier to debug.

The base URL for all ShipStation API calls is `https://ssapi.shipstation.com`. All endpoints use this domain with paths like `/orders`, `/shipments`, `/carriers`, etc. Add both credentials to your .env file as shown below — never commit them to Git or use NEXT_PUBLIC_ prefix.

```
// lib/shipstation.ts
const BASE_URL = 'https://ssapi.shipstation.com';

function getAuthHeader(): string {
  const key = process.env.SHIPSTATION_API_KEY;
  const secret = process.env.SHIPSTATION_API_SECRET;
  if (!key || !secret) {
    throw new Error('SHIPSTATION_API_KEY and SHIPSTATION_API_SECRET must be set');
  }
  return 'Basic ' + Buffer.from(`${key}:${secret}`).toString('base64');
}

export async function shipstationFetch<T = unknown>(
  endpoint: string,
  init: RequestInit = {}
): Promise<T> {
  const url = `${BASE_URL}${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;
  const response = await fetch(url, {
    ...init,
    headers: {
      'Authorization': getAuthHeader(),
      'Content-Type': 'application/json',
      ...init.headers,
    },
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`ShipStation API error ${response.status}: ${errorText}`);
  }

  return response.json() as Promise<T>;
}
```

**Expected result:** A configured ShipStation authentication helper in lib/shipstation.ts and credentials stored in .env, ready for use in API routes.

### 2. Fetch and display orders from ShipStation

The ShipStation orders endpoint (`GET /orders`) returns paginated order data including recipient details, line items, weight, and current order status. You will build an API route that fetches orders filtered by status and passes them to a React dashboard component.

ShipStation orders have several status values you will encounter: `awaiting_payment` (order received, payment not confirmed), `awaiting_shipment` (paid and ready to fulfill — this is the most important queue for fulfillment teams), `shipped` (label created and tracking number assigned), `on_hold`, and `cancelled`. Most fulfillment dashboards focus on `awaiting_shipment` orders.

The endpoint supports extensive filtering via query parameters: `orderStatus`, `orderDateStart`, `orderDateEnd`, `storeId` (to filter by sales channel), `sortBy` (OrderDate, ModifyDate), and `sortDir` (ASC or DESC). For pagination, use `page` (1-indexed) and `pageSize` (max 500). A typical dashboard request fetches the first page of `awaiting_shipment` orders sorted by oldest first.

The response JSON includes `orders` (array), `total` (total matching orders across all pages), `page`, and `pages`. Build your API route to pass through the full response so your frontend can show pagination controls when there are many orders.

In the React component, display the key fields your team needs at a glance: order number, customer name, shipping destination state/country, order total, and item count. A well-organized fulfillment table dramatically speeds up pick-and-pack workflows compared to the default ShipStation interface.

```
// app/api/shipstation/orders/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { shipstationFetch } from '@/lib/shipstation';

interface ShipStationOrdersResponse {
  orders: unknown[];
  total: number;
  page: number;
  pages: number;
}

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const status = searchParams.get('status') ?? 'awaiting_shipment';
  const page = searchParams.get('page') ?? '1';
  const pageSize = searchParams.get('pageSize') ?? '50';

  const params = new URLSearchParams({
    orderStatus: status,
    page,
    pageSize,
    sortBy: 'OrderDate',
    sortDir: 'ASC',
  });

  const data = await shipstationFetch<ShipStationOrdersResponse>(
    `/orders?${params.toString()}`
  );

  return NextResponse.json(data);
}
```

**Expected result:** An API route that returns paginated ShipStation orders filtered by status, and a dashboard table displaying pending shipments with key fulfillment details.

### 3. Shop carrier rates and create shipping labels

Rate shopping and label creation are the two most valuable ShipStation API capabilities. Instead of logging into ShipStation and manually comparing carrier options, your app can present rates automatically and let users select one to instantly generate a label.

For rate shopping, the endpoint is `POST /carriers/listrates`. The request body requires `carrierCode` (or use the special value `stamps_com` for USPS), `serviceCode` (optional — omit to get all services), `fromPostalCode`, `toPostalCode`, `toCountry` (ISO 2-letter code), `weight` (object with `value` and `units` — use `ounces`), and optionally `dimensions` (length, width, height in inches). The response is an array of rate objects, each with `carrierCode`, `serviceCode`, `serviceName`, `shipDate`, `deliveryDays`, and `shipmentCost`. Sort by `shipmentCost` ascending to show cheapest first.

For label creation, use `POST /orders/createlabelfororder`. You pass the `orderId` (from the orders list), `carrierCode`, `serviceCode`, and `confirmation` (e.g., `none`, `delivery`, `signature`). ShipStation creates the label in your connected carrier account and returns `shipmentId`, `trackingNumber`, `labelData` (Base64-encoded PDF), and `labelFileToken`. Save the tracking number to your database so you can surface it in order confirmations.

Important: calling `createlabelfororder` deducts from your connected carrier account (e.g., Stamps.com credits or UPS account balance) immediately. Use a test carrier or sandbox account during development — ShipStation does not have a separate sandbox environment, so label creation is always live.

For rate-only requests (no purchase), ShipStation's rates API does not charge anything — it is a pure quote API.

```
// app/api/shipstation/rates/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { shipstationFetch } from '@/lib/shipstation';

interface RateResponse {
  carrierCode: string;
  serviceCode: string;
  serviceName: string;
  shipDate: string;
  deliveryDays: number;
  shipmentCost: number;
  otherCost: number;
}

export async function POST(request: NextRequest) {
  const { fromPostalCode, toPostalCode, toCountry, weightOz, dimensions } =
    await request.json();

  if (!fromPostalCode || !toPostalCode || !weightOz) {
    return NextResponse.json(
      { error: 'fromPostalCode, toPostalCode, and weightOz are required' },
      { status: 400 }
    );
  }

  const body: Record<string, unknown> = {
    carrierCode: 'stamps_com',
    fromPostalCode,
    toPostalCode,
    toCountry: toCountry ?? 'US',
    weight: { value: weightOz, units: 'ounces' },
  };

  if (dimensions) {
    body.dimensions = {
      length: dimensions.length,
      width: dimensions.width,
      height: dimensions.height,
      units: 'inches',
    };
  }

  const rates = await shipstationFetch<RateResponse[]>('/carriers/listrates', {
    method: 'POST',
    body: JSON.stringify(body),
  });

  const sorted = [...rates].sort((a, b) => a.shipmentCost - b.shipmentCost);
  return NextResponse.json({ rates: sorted });
}
```

**Expected result:** A rate comparison API that returns carrier rates sorted by cost, and a label creation API that generates shipping labels and returns tracking numbers.

### 4. Deploy to Netlify and configure ShipStation webhooks

During development in Bolt.new's WebContainer, all outbound calls to ShipStation work perfectly — you can fetch orders, shop rates, and create labels without any issues. The WebContainer limitation only applies to incoming connections, which affects ShipStation's webhook system.

ShipStation webhooks send POST requests to your registered URL when events occur: `ORDER_NOTIFY` (new order arrives), `SHIP_NOTIFY` (shipment created with tracking), `ITEM_ORDER_NOTIFY`, and several others. These real-time notifications are what allow your app to react to fulfillment events — updating order status in your database, sending shipping confirmation emails to customers, or updating inventory levels — without polling the ShipStation API every few minutes.

To deploy: click Deploy in Bolt.new and connect to Netlify. After deployment, go to your Netlify dashboard → Site Configuration → Environment Variables and add `SHIPSTATION_API_KEY`, `SHIPSTATION_API_SECRET`, and any other variables you have in .env. Trigger a redeploy.

To configure webhooks in ShipStation: go to Account → Account Settings → Webhooks → Add Webhook. Enter your Netlify URL for the webhook endpoint (e.g., `https://your-app.netlify.app/api/shipstation/webhook`), select the event type (`SHIP_NOTIFY` is most commonly needed), choose the store to monitor, and save. ShipStation sends a test POST immediately — your handler should return 200.

Webhook payloads from ShipStation include a `resource_url` field rather than the full event data — you must make a second API call to that URL to get the actual shipment or order details. This two-step pattern is intentional and reduces payload size for high-volume accounts.

```
// app/api/shipstation/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { shipstationFetch } from '@/lib/shipstation';

interface ShipStationWebhookBody {
  resource_url: string;
  resource_type: string;
}

interface ShipmentResource {
  shipments: Array<{
    shipmentId: number;
    orderId: number;
    orderKey: string;
    trackingNumber: string;
    carrierCode: string;
    serviceCode: string;
    shipDate: string;
    shipmentCost: number;
  }>;
}

// NOTE: ShipStation webhooks require a publicly deployed URL.
// During Bolt.new WebContainer development, incoming webhooks cannot
// be received. Deploy to Netlify first, then register the webhook URL
// in ShipStation Account Settings → Webhooks.
export async function POST(request: NextRequest) {
  const body: ShipStationWebhookBody = await request.json();
  const { resource_type, resource_url } = body;

  if (resource_type === 'SHIP_NOTIFY' && resource_url) {
    // ShipStation sends resource_url — we must fetch the actual data
    const endpoint = resource_url.replace('https://ssapi.shipstation.com', '');
    const data = await shipstationFetch<ShipmentResource>(endpoint);

    for (const shipment of data.shipments ?? []) {
      console.log(
        `Shipment created: order ${shipment.orderKey}, ` +
        `tracking ${shipment.trackingNumber} via ${shipment.carrierCode}`
      );
      // TODO: Update your database and send tracking email to customer
    }
  }

  // Always return 200 — ShipStation retries on non-200 responses
  return NextResponse.json({ received: true });
}
```

**Expected result:** A deployed app on Netlify with a webhook handler that receives ShipStation shipment notifications and logs tracking numbers for further processing.

## Best practices

- Store SHIPSTATION_API_KEY and SHIPSTATION_API_SECRET only in server-side environment variables — never prefix them with NEXT_PUBLIC_ or access them from React components
- Implement an in-memory or Redis cache for order listings since the ShipStation API has a 40 requests/minute rate limit and dashboard views can trigger many requests
- Always return HTTP 200 from webhook handlers even when processing fails — ShipStation retries non-200 responses repeatedly, leading to duplicate processing
- Use the resource_url pattern for webhooks: ShipStation sends only a URL in webhook payloads, not the full data — always make a follow-up GET request to retrieve the actual shipment or order details
- Test rate shopping before label creation — always get a rate quote first to confirm service availability before calling createlabelfororder, which charges your carrier account immediately
- Validate required shipping fields (postal codes, country codes, weight) in your API route before calling ShipStation to provide helpful error messages to users instead of forwarding cryptic API errors
- Use ShipStation's pagination parameters (page and pageSize up to 500) rather than fetching all orders in one call — large stores can have thousands of awaiting_shipment orders
- Log tracking numbers to your own database immediately after label creation — ShipStation is the system of record for labels, but your app should have a local copy for order confirmation emails and customer-facing tracking pages

## Use cases

### Shipping Rate Comparison Dashboard

Build a dashboard that accepts order dimensions and destination, then queries ShipStation for rate quotes across all connected carriers in one call. Display USPS, UPS, and FedEx rates side by side so your fulfillment team can always pick the cheapest or fastest option at a glance.

Prompt example:

```
Build a shipping rate comparison tool in my Next.js app. Create an API route at /api/shipstation/rates that accepts POST with weight (oz), dimensions (length, width, height in inches), fromZip, toZip, and toCountry. Call ShipStation's carrier/listrates endpoint using HTTP Basic auth with SHIPSTATION_API_KEY and SHIPSTATION_API_SECRET from process.env. Return the rates array sorted by shipmentCost ascending. Create a React component with a form for these inputs and a results table showing carrier name, service name, days, and cost. Show a loading spinner during the request.
```

### Order Fulfillment Queue

Create an internal fulfillment dashboard that pulls all awaiting_shipment orders from ShipStation and displays them in a sortable table. Your fulfillment team can review orders, select a carrier service, and trigger label creation — all from your custom admin interface.

Prompt example:

```
Create an order fulfillment dashboard. Build an API route at /api/shipstation/orders that fetches orders from ShipStation with orderStatus=awaiting_shipment using HTTP Basic auth (SHIPSTATION_API_KEY:SHIPSTATION_API_SECRET Base64-encoded). Also create /api/shipstation/create-label that accepts POST with orderId and carrierCode, calls ShipStation's orders/createlabelfororder endpoint, and returns the labelData with trackingNumber and labelUrl. Build a React page with a table of pending orders, a carrier selector per row, and a Create Label button that calls the label endpoint and shows the tracking number on success.
```

### Automated Post-Purchase Label Creation

After a customer completes checkout in your Bolt app, automatically push the order to ShipStation and trigger label creation for pre-configured carrier services. This removes the manual step of logging into ShipStation to process orders.

Prompt example:

```
After a successful Stripe payment in my app, automatically create a ShipStation order. Build an API route at /api/shipstation/push-order that accepts POST with customer details (name, address, email), line items (sku, name, quantity, price), and order total. Call ShipStation POST /orders/createorder to create the order, then immediately call POST /orders/createlabelfororder with the returned orderId and a default carrier from SHIPSTATION_DEFAULT_CARRIER env var. Return the trackingNumber and labelUrl to the frontend for display on the confirmation page.
```

## Troubleshooting

### API calls return 401 Unauthorized even though credentials appear correct

Cause: HTTP Basic auth requires the credentials to be Base64-encoded as 'apiKey:apiSecret' (colon separator). A common mistake is encoding them separately or swapping key and secret order. Another cause is whitespace or newline characters accidentally included when copying credentials from the ShipStation dashboard.

Solution: Verify your encoding: in Node.js, run Buffer.from('yourkey:yoursecret').toString('base64') in a test script and compare to what your code generates. Also check that SHIPSTATION_API_KEY contains the key (shorter string) and SHIPSTATION_API_SECRET contains the secret (longer string) — they are sometimes confused. Regenerate API keys in Account → Account Settings → API Settings if issues persist.

```
// Quick test in a Node.js script or Bolt terminal
const key = process.env.SHIPSTATION_API_KEY;
const secret = process.env.SHIPSTATION_API_SECRET;
console.log('Auth header:', 'Basic ' + Buffer.from(`${key}:${secret}`).toString('base64'));
```

### Rate quotes return an empty array or 'carrier not found' error

Cause: ShipStation only returns rates for carriers that are connected to your account. If you haven't connected a USPS, UPS, or FedEx account in ShipStation's Carriers settings, the rates API returns nothing for that carrier code.

Solution: In ShipStation, go to Account → Carriers and connect at least one carrier. For USPS, use Stamps.com (built in) with carrierCode 'stamps_com'. For UPS, use carrierCode 'ups'. After connecting a carrier, retry the rates API call. You can also call GET /carriers to list all connected carriers and their codes programmatically.

### ShipStation webhooks are not arriving during Bolt.new development

Cause: Bolt.new runs in a WebContainer — a browser-based Node.js runtime that does not have a persistent public URL. ShipStation webhooks send POST requests to your registered endpoint, which requires a publicly accessible server. The Bolt.new preview URL is not reachable by external services.

Solution: Deploy your app to Netlify first (click Deploy in Bolt.new), then register the deployed URL in ShipStation Account Settings → Webhooks. Use your Netlify domain like https://your-app.netlify.app/api/shipstation/webhook. This is a fundamental WebContainer limitation — webhooks always require a deployed environment.

### Rate limit errors (429 Too Many Requests) appearing in the API logs

Cause: ShipStation allows 40 API calls per minute. Dashboard components that fetch orders on every render, or polling intervals set too frequently, quickly exhaust this limit — especially during active development when the browser refreshes often.

Solution: Add caching to your API routes using Next.js revalidate config or an in-memory cache. Set the route segment to revalidate every 60 seconds so repeated client requests within the same minute hit the cache instead of ShipStation's API. For dashboards, implement a manual refresh button rather than auto-polling.

```
// app/api/shipstation/orders/route.ts — add at top
export const revalidate = 60; // Cache for 60 seconds
```

## Frequently asked questions

### Can I use ShipStation's API in Bolt.new without deploying?

Yes, for all outbound API calls. Fetching orders, shopping carrier rates, and creating labels all involve outbound HTTP calls from your Bolt.new API route to ShipStation's servers — these work perfectly in Bolt's WebContainer during development. The only capability that requires deployment is ShipStation webhooks, which send incoming POST requests that WebContainers cannot receive.

### Does ShipStation have a sandbox or test environment?

No, ShipStation does not offer a separate sandbox environment. All API calls are made against your live account. Rate quotes are free and non-destructive, but label creation immediately charges your connected carrier account. Use ShipStation's 'Void Label' feature after testing to cancel test labels. Some developers create a second ShipStation account with no carrier balance for pure API testing.

### How do I get rates for multiple carriers at once?

The POST /carriers/listrates endpoint accepts a single carrierCode per request. To compare across all carriers, you need to make separate requests for each connected carrier (stamps_com, ups, fedex, etc.) and combine the results. Alternatively, call GET /carriers first to get your list of connected carrier codes, then make parallel rate requests using Promise.all() in your API route.

### What ShipStation API authentication method should I use in Bolt.new?

Use HTTP Basic authentication — Base64-encode your API key and secret in the format key:secret and pass it as the Authorization header. The encoding is done with Buffer.from('key:secret').toString('base64') in Node.js. Always do this encoding inside an API route where process.env variables are available server-side, never in client-side React code.

### How do I avoid hitting ShipStation's 40 requests/minute rate limit?

Add caching to your API routes using Next.js route segment revalidation (export const revalidate = 60) to serve cached responses for up to 60 seconds. For dashboards that display order counts and status, a 60-second cache is imperceptible to users. Also replace auto-polling with manual refresh buttons where possible, since frequent polling is the most common cause of rate limit errors.

### Can ShipStation webhooks be tested locally in Bolt.new?

No — ShipStation webhooks send POST requests to your registered URL, and Bolt.new's WebContainer has no persistent public URL that external services can reach. To test webhooks, deploy your app to Netlify or Vercel, register the deployed URL in ShipStation Account Settings → Webhooks, and trigger a test event. Alternatively, use a tunneling service like ngrok with a locally exported Bolt project.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/shipstation
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/shipstation
