# How to Integrate Bolt.new with ShipBob

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

## TL;DR

To integrate ShipBob with Bolt.new, create Next.js API routes that call ShipBob's REST API using an OAuth 2.0 access token from the ShipBob Developer Portal. Fetch inventory levels across fulfillment centers, create and track orders, and build a fulfillment dashboard. All outbound API calls work in Bolt's WebContainer preview. ShipBob webhooks (order status updates) require a deployed URL.

## Building a Fulfillment Management Dashboard with ShipBob and Bolt.new

ShipBob is one of the largest third-party logistics (3PL) providers for e-commerce, handling fulfillment for thousands of direct-to-consumer brands. If you are building an e-commerce app, subscription box service, or order management system, integrating ShipBob's API lets you synchronize inventory levels, submit orders for fulfillment, track shipment status, and manage returns — all programmatically without logging into the ShipBob dashboard manually.

ShipBob's REST API uses OAuth 2.0 for authentication. Unlike simpler API key integrations, OAuth 2.0 issues time-limited access tokens that must be refreshed. For merchant apps (your own ShipBob account), you can generate a Personal Access Token in the ShipBob Developer Portal that acts like a long-lived access token — simpler to manage for single-account integrations. For apps that connect to multiple merchants' ShipBob accounts, you need the full OAuth 2.0 authorization code flow. This tutorial covers both scenarios.

The ShipBob API covers four main resource types: inventory (SKU-level stock across fulfillment centers), products (your product catalog in ShipBob), orders (fulfillment requests you submit), and shipments (the physical shipments ShipBob creates). Building a fulfillment dashboard in Bolt means querying inventory to show stock levels, creating orders when customers purchase, and polling shipment endpoints to display tracking numbers and carrier updates. All these outbound API calls work in Bolt's WebContainer preview.

## Before you start

- A ShipBob account with API access enabled (Merchant plan or above)
- A ShipBob Personal Access Token from the Developer Portal at developer.shipbob.com
- Products and inventory already set up in your ShipBob account
- A Bolt.new project using Next.js for server-side API routes
- For webhook integration: a deployed URL from Netlify or Bolt Cloud

## Step-by-step guide

### 1. Get ShipBob API Credentials

ShipBob uses OAuth 2.0 for all API authentication. For integrating your own ShipBob account, the simplest approach is a Personal Access Token (PAT). Log into your ShipBob account, navigate to the Integrations section, then Developer Tools (or go directly to developer.shipbob.com). Under 'Personal Access Tokens', generate a new token with the scopes you need: read:inventory, write:inventory, read:order, write:order, read:shipment are the core scopes for most integrations.

Personal Access Tokens are long-lived and do not expire automatically (you can revoke them). They authenticate as you with your account permissions. This is the right approach for connecting your own ShipBob account to your app. If you are building an app that other ShipBob merchants will connect to (a multi-tenant integration), you need the full OAuth 2.0 authorization code flow — ShipBob has a developer program at developer.shipbob.com where you register an application and get a client ID and client secret for the standard OAuth 2.0 flow.

ShipBob's API base URL is https://api.shipbob.com and the current API version is v2.0 (2.0). All endpoints follow the pattern https://api.shipbob.com/2.0/{resource}. Authentication uses the Authorization: Bearer {token} header. The API returns JSON for all responses and uses standard HTTP status codes.

```
# ShipBob API base URL and authentication pattern:
# Base URL: https://api.shipbob.com/2.0/
# Authentication: Authorization: Bearer {YOUR_ACCESS_TOKEN}

# Key endpoints:
# GET  /2.0/inventory           — list all inventory (paginated)
# GET  /2.0/inventory/{id}      — single inventory item
# GET  /2.0/product             — list products
# POST /2.0/order               — create fulfillment order
# GET  /2.0/order/{id}          — get order status
# GET  /2.0/shipment            — list shipments (filterable by orderId)
# GET  /2.0/shipment/{id}       — single shipment with tracking
```

**Expected result:** You have a ShipBob Personal Access Token with the required scopes. The token is ready to add to your .env file.

### 2. Configure Environment Variables and Create a ShipBob Client

Create a ShipBob API client utility that centralizes authentication and request handling. All ShipBob API calls are server-side — the access token must never be exposed in client-side JavaScript. In Next.js, server-only environment variables (no NEXT_PUBLIC_ prefix) are only accessible in API routes and server components, which is exactly where ShipBob calls should live.

The ShipBob API client wraps fetch with the Authorization header and base URL, handles pagination for list endpoints (ShipBob uses page and limit query parameters), and provides consistent error handling. ShipBob returns error details in the response body as a JSON object with a message field — parse these to surface helpful error messages rather than generic 500 errors.

Pagination is important for inventory and order endpoints if you have many SKUs or orders. ShipBob's default page size is 250 items, and you can request up to 250 per page (the maximum). For large catalogs, implement pagination in your API routes to avoid fetching thousands of records on every request — instead, pass page and limit parameters from the frontend to your API route and forward them to ShipBob.

```
// lib/shipbob.ts
const BASE_URL = 'https://api.shipbob.com/2.0';

async function shipbobFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
  const token = process.env.SHIPBOB_ACCESS_TOKEN;
  if (!token) throw new Error('SHIPBOB_ACCESS_TOKEN is not configured');

  const response = await fetch(`${BASE_URL}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if (!response.ok) {
    const error = await response.json().catch(() => ({ message: response.statusText }));
    throw new Error(`ShipBob API ${response.status}: ${error.message || JSON.stringify(error)}`);
  }

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

export interface ShipBobInventoryItem {
  id: number;
  name: string;
  is_active: boolean;
  total_fulfillable_quantity: number;
  total_onhand_quantity: number;
  total_committed_quantity: number;
  fulfillable_quantity_by_fulfillment_center: Array<{
    id: number;
    name: string;
    fulfillable_quantity: number;
  }>;
}

export const shipbob = {
  getInventory: (page = 1, limit = 100) =>
    shipbobFetch<ShipBobInventoryItem[]>(`/inventory?Page=${page}&Limit=${limit}`),

  createOrder: (orderData: object) =>
    shipbobFetch('/order', { method: 'POST', body: JSON.stringify(orderData) }),

  getOrder: (id: string) => shipbobFetch(`/order/${id}`),

  listShipments: (orderId?: string) =>
    shipbobFetch(`/shipment${orderId ? `?OrderId=${orderId}` : ''}`),
};
```

**Expected result:** lib/shipbob.ts provides a clean API client. SHIPBOB_ACCESS_TOKEN is in .env. The client is ready to import into API routes.

### 3. Build Inventory and Order API Routes

With the ShipBob client ready, create the API routes your frontend dashboard will call. The inventory endpoint is the most commonly needed — it returns all your products in ShipBob with their current stock levels across fulfillment centers. The order creation endpoint converts your app's order data into ShipBob's expected format and submits the fulfillment request.

The ShipBob order creation API requires specific formatting: recipient (shipping address), products (array with ShipBob's internal product ID and quantity), and optional fields like reference_id (your order number), shipping_method, and order_date. Getting the ShipBob product IDs right is critical — they are ShipBob's internal integer IDs, not your own SKUs. Fetch your product list first and maintain a mapping from your SKUs to ShipBob product IDs.

For the inventory dashboard, ShipBob's response includes total_fulfillable_quantity (what can be shipped now) and fulfillable_quantity_by_fulfillment_center (breakdown per warehouse). Display both — total tells you if you can fulfill an order, the per-center breakdown helps with restock planning.

```
// app/api/shipbob/orders/route.ts
import { NextResponse } from 'next/server';
import { shipbob } from '@/lib/shipbob';

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const { referenceId, recipient, products } = body;

    if (!recipient || !products || products.length === 0) {
      return NextResponse.json(
        { error: 'recipient and products are required' },
        { status: 400 }
      );
    }

    const shipbobOrder = await shipbob.createOrder({
      reference_id: referenceId,
      order_date: new Date().toISOString(),
      shipping_method: 'Standard',
      recipient: {
        name: recipient.name,
        address: {
          address1: recipient.address1,
          city: recipient.city,
          state: recipient.state,
          zip_code: recipient.zipCode,
          country: recipient.country || 'US',
        },
      },
      products: products.map((p: { productId: number; quantity: number }) => ({
        id: p.productId,
        quantity: p.quantity,
        reference_id: referenceId,
      })),
    });

    return NextResponse.json(shipbobOrder, { status: 201 });
  } catch (error: unknown) {
    const e = error as { message: string };
    return NextResponse.json({ error: e.message }, { status: 500 });
  }
}
```

**Expected result:** GET /api/shipbob/inventory returns your product inventory with quantities across fulfillment centers. POST /api/shipbob/orders creates a real ShipBob fulfillment order and returns the order ID. Both work in the Bolt preview.

### 4. Build the Fulfillment Dashboard UI

The fulfillment dashboard brings the inventory and order data together in a visual interface. The core views are: an inventory table showing all products with their stock levels, highlighted low-stock items, and a per-fulfillment-center breakdown; an orders table showing recent orders with their status; and a shipment detail view for tracking.

For the inventory table, display the fulfillable quantity (available to ship immediately) prominently — this is the most actionable number. Show committed quantity (reserved for open orders) and total on-hand quantity as secondary data. Highlight rows where fulfillable quantity falls below a configurable threshold (10-20 units is a common alert level).

For the orders view, ShipBob orders have statuses like Processing, Partially Fulfilled, Fulfilled, Exception, Cancelled. Show status badges with appropriate colors. The transition from Processing to Fulfilled is the key event — at this point, a shipment is created and tracking information becomes available.

Refresh strategies: inventory data changes throughout the day as orders are fulfilled. Polling every 5 minutes is reasonable for a dashboard. For more real-time updates, ShipBob webhooks push status changes to your app, but webhooks require a deployed URL (not available in the Bolt preview).

**Expected result:** The fulfillment dashboard shows live inventory levels from ShipBob with low-stock highlighting. The orders tab displays recent orders and their fulfillment status. Both tabs auto-refresh every 5 minutes.

### 5. Deploy and Configure ShipBob Webhooks

All the API routes built so far use outbound calls from your app to ShipBob's API — these work fully in Bolt's WebContainer preview. ShipBob webhooks work in the opposite direction: ShipBob sends HTTP POST requests to your app when events occur (order created, shipment created, order cancelled, inventory updated). Since Bolt's WebContainer has no public URL, you cannot receive ShipBob webhooks during development. Deploy first, then configure webhooks.

Deploy to Netlify via Settings → Applications → Netlify in your Bolt project. After deploying, add SHIPBOB_ACCESS_TOKEN to Netlify's environment variables (Site Settings → Environment Variables) and trigger a redeploy. Your deployed app URL will be something like https://your-app-name.netlify.app.

To configure ShipBob webhooks: go to the ShipBob Developer Portal, navigate to Webhooks, and create a new webhook subscription. Enter your deployed URL as the endpoint (e.g., https://your-app-name.netlify.app/api/shipbob/webhook), select the event types you want to receive (order_shipped, shipment_delivered, inventory_quantity_changed are the most useful), and save. ShipBob will send a test event to verify the endpoint is reachable. Create the webhook handler route in Bolt, deploy again, and you're receiving real-time fulfillment events.

```
// app/api/shipbob/webhook/route.ts
import { NextResponse } from 'next/server';

interface ShipBobWebhookPayload {
  topic: string;
  data: Record<string, unknown>;
}

export async function POST(request: Request) {
  try {
    const payload = await request.json() as ShipBobWebhookPayload;
    const { topic, data } = payload;

    console.log(`ShipBob webhook received: ${topic}`, JSON.stringify(data).slice(0, 200));

    switch (topic) {
      case 'order_shipped':
        // Update order status and store tracking number
        // await updateOrderStatus(data.order_id, 'shipped', data.tracking_number);
        break;
      case 'shipment_delivered':
        // Mark order as delivered
        // await updateOrderStatus(data.order_id, 'delivered');
        break;
      case 'inventory_quantity_changed':
        // Log inventory change for dashboard refresh
        console.log('Inventory change for product:', data.product_id);
        break;
      default:
        console.log(`Unhandled ShipBob event: ${topic}`);
    }

    // Always return 200 quickly — ShipBob retries if it doesn't receive 200
    return NextResponse.json({ received: true });
  } catch (error: unknown) {
    const e = error as { message: string };
    console.error('Webhook processing error:', e.message);
    // Still return 200 to prevent ShipBob retries for parse errors
    return NextResponse.json({ received: true });
  }
}
```

**Expected result:** After deploying and configuring the ShipBob webhook URL, your app receives real-time order and inventory events. The webhook handler logs events and updates your database, keeping your dashboard current without polling.

## Best practices

- Store SHIPBOB_ACCESS_TOKEN only as a server-side environment variable — never add NEXT_PUBLIC_ prefix since this would expose it to client-side JavaScript
- Cache the ShipBob product ID to SKU mapping in your database or memory — product IDs rarely change and fetching the full product list on every order creation wastes API quota
- Return 200 immediately from webhook handlers and process events asynchronously — ShipBob retries failed webhooks 3 times and marks your endpoint as unhealthy if it consistently fails
- Implement idempotent webhook processing by storing the ShipBob event ID and skipping duplicate events — ShipBob may deliver the same webhook more than once
- Use ShipBob's reference_id field to store your internal order ID — this allows bidirectional lookup between your orders and ShipBob's order IDs
- Poll inventory only at intervals that make sense for your business (every 5-15 minutes for dashboards) rather than on every page load — ShipBob's rate limits are generous but unnecessary polling wastes them
- Test order creation with ShipBob's sandbox environment before connecting to your live account — the sandbox uses the same API but doesn't trigger real fulfillment

## Use cases

### Inventory Dashboard Across Fulfillment Centers

Build a real-time inventory dashboard showing stock levels for each SKU across all ShipBob fulfillment centers. Track which centers are running low, see total inventory by product, and identify fulfillment center imbalances that require stock transfers. Query the ShipBob inventory API and display results in a filterable table.

Prompt example:

```
Build an inventory dashboard for ShipBob. Create an API route at /api/shipbob/inventory that calls GET /2.0/inventory from the ShipBob API using SHIPBOB_ACCESS_TOKEN. Display a table with columns: Product Name, SKU, Total Quantity, and a breakdown by fulfillment center. Add a search input to filter by product name or SKU. Show low stock alerts (red badge) for items with less than 10 units total. Refresh every 5 minutes automatically.
```

### Automated Order Submission from E-Commerce App

When a customer completes a purchase in your app, automatically submit the order to ShipBob for fulfillment. The API route takes the order details (customer address, line items with quantities), creates a ShipBob order, and stores the ShipBob order ID for tracking. This replaces manual order entry in the ShipBob dashboard.

Prompt example:

```
Create an API route at /api/shipbob/orders (POST) that creates a ShipBob fulfillment order. Accept: shippingAddress (name, address1, city, state, zip, country), lineItems (array of {productId, quantity}), and referenceId (your order ID). Call POST /2.0/order on the ShipBob API with the data formatted to ShipBob's schema. Return the ShipBob order ID and estimated ship date. Store the ShipBob order ID in the database linked to your order. Use SHIPBOB_ACCESS_TOKEN from environment variables.
```

### Shipment Tracking and Returns Portal

Build a customer-facing order status page that shows real-time shipment information from ShipBob. Customers enter their order number, and the app fetches the ShipBob shipment status, tracking number, and carrier. Also provide a returns initiation flow that calls the ShipBob returns API to generate return labels.

Prompt example:

```
Build a shipment tracking page. Create an API route at /api/shipbob/shipments/[orderId] that calls GET /2.0/shipment?orderId={id} from ShipBob to get shipment status and tracking number for a given order. Display: current status (processing/shipped/delivered), carrier name, tracking number with a link to the carrier tracking page, estimated delivery date, and fulfillment center. Use SHIPBOB_ACCESS_TOKEN from environment variables. Add a 'Track Order' input form on the page.
```

## Troubleshooting

### API returns 401 Unauthorized or 'invalid_token'

Cause: SHIPBOB_ACCESS_TOKEN is incorrect, expired, or revoked. Personal Access Tokens can be revoked manually in the Developer Portal.

Solution: Go to developer.shipbob.com → Personal Access Tokens and verify your token is still active (not revoked). Generate a new token if needed. Ensure SHIPBOB_ACCESS_TOKEN in your .env file matches exactly — no extra spaces or newline characters. Restart the Bolt dev server after editing .env.

### Order creation returns 422 Unprocessable Entity

Cause: ShipBob validation rejected the order data. Common causes: invalid product ID (ShipBob's internal ID, not your SKU), shipping address country code not in ISO 3166-1 alpha-2 format (e.g., 'US' not 'USA'), or missing required fields.

Solution: Check the error response body for ShipBob's validation message — it specifies which field failed. Fetch your ShipBob product list (GET /2.0/product) to get the correct integer product IDs. Ensure country codes are two-letter ISO codes. Verify all required order fields are present: recipient.address, products array with at least one item.

```
// Fetch ShipBob product IDs before creating orders:
const products = await shipbobFetch<{ id: number; name: string; reference_id: string }[]>('/product');
// Map from your SKU to ShipBob's integer ID:
const skuToId = Object.fromEntries(products.map(p => [p.reference_id, p.id]));
```

### ShipBob webhooks not arriving during Bolt preview development

Cause: This is the expected WebContainer limitation. ShipBob webhooks make outbound HTTP requests to your endpoint. Bolt's WebContainer has no public URL, so ShipBob cannot reach it.

Solution: Deploy to Netlify or Bolt Cloud first. Configure the ShipBob webhook URL to point to your deployed endpoint (https://your-app.netlify.app/api/shipbob/webhook). Test webhooks using ShipBob's test event feature in the Developer Portal. During development, poll the ShipBob API for status updates instead of waiting for webhooks.

### Inventory endpoint returns empty array despite having products in ShipBob

Cause: Products must be 'active' in ShipBob to appear in the inventory endpoint by default. Archived or inactive products are excluded. Also, newly created products without inventory transactions may not appear.

Solution: Add ?Page=1&Limit=250&IsActive=true to your inventory request to explicitly filter for active inventory. If products are still missing, check in the ShipBob dashboard that the products have inventory received — products created but with no stock don't always appear in inventory queries.

```
// Explicitly request active inventory:
const inventory = await shipbobFetch('/inventory?Page=1&Limit=250&IsActive=true');
```

## Frequently asked questions

### Does the ShipBob API work in Bolt's WebContainer during development?

Yes. All outbound ShipBob API calls (fetching inventory, creating orders, getting shipment status) work in Bolt's WebContainer preview since they communicate over HTTPS. The limitation is incoming connections — ShipBob webhooks cannot reach the WebContainer since it has no public URL. For real-time updates during development, poll the ShipBob API at intervals instead of relying on webhooks.

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

Yes. ShipBob provides a sandbox environment at app.sandbox.shipbob.com with its own API base URL (api.sandbox.shipbob.com) and separate credentials. The sandbox lets you create test orders, simulate shipments, and test webhooks without affecting your production ShipBob account. Register at developer.shipbob.com to access sandbox credentials.

### How do I get ShipBob product IDs for order creation?

ShipBob uses its own integer IDs for products, separate from your SKUs. Fetch your product list with GET /2.0/product — each product in the response has an id (ShipBob's integer ID) and a reference_id (which you can set to your own SKU when creating products). Build a mapping from your SKUs to ShipBob product IDs and store it in your database. The mapping rarely changes unless you create or archive products.

### Can I use ShipBob to fulfill orders from multiple e-commerce platforms (Shopify, WooCommerce) through one Bolt app?

Yes. A common pattern is a central Bolt-based fulfillment hub that receives orders from multiple platforms via their APIs or webhooks, normalizes the data, and submits them to ShipBob through a single integration. Each platform sends orders to your app, and your API route maps them to ShipBob's order schema. The ShipBob order reference_id field stores the source platform's order ID for tracking.

### How does ShipBob pricing work for the API?

ShipBob's API access is included with all merchant accounts — there is no separate API fee. You pay ShipBob for their fulfillment services (per-order fees, storage fees, receiving fees) and get full API access as part of the service. API rate limits are generous for most applications, with limits on requests per second rather than total monthly calls.

---

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