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.
Integration method
ShipBob uses OAuth 2.0 for authentication, issuing access tokens that authorize API calls to inventory, order, and shipment endpoints. All ShipBob REST API calls are outbound HTTPS requests from your Next.js API routes and work in Bolt's WebContainer preview. ShipBob webhooks for real-time order status updates require a publicly accessible URL and must be configured after deploying to Netlify or Bolt Cloud.
Prerequisites
- 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
Get ShipBob API Credentials
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.
1# ShipBob API base URL and authentication pattern:2# Base URL: https://api.shipbob.com/2.0/3# Authentication: Authorization: Bearer {YOUR_ACCESS_TOKEN}45# Key endpoints:6# GET /2.0/inventory — list all inventory (paginated)7# GET /2.0/inventory/{id} — single inventory item8# GET /2.0/product — list products9# POST /2.0/order — create fulfillment order10# GET /2.0/order/{id} — get order status11# GET /2.0/shipment — list shipments (filterable by orderId)12# GET /2.0/shipment/{id} — single shipment with trackingPro tip: Copy your Personal Access Token immediately after generating it — ShipBob only shows it once. Store it in your password manager and your .env file. If lost, revoke the old token and generate a new one.
Expected result: You have a ShipBob Personal Access Token with the required scopes. The token is ready to add to your .env file.
Configure Environment Variables and Create a ShipBob Client
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.
Create a lib/shipbob.ts utility for the ShipBob API. Include a shipbobFetch function that adds Authorization: Bearer {SHIPBOB_ACCESS_TOKEN} header and the base URL https://api.shipbob.com/2.0. Export helper functions: getInventory(page?, limit?), getProduct(id), listOrders(page?, referenceId?), createOrder(orderData), getShipment(id), listShipments(orderId?). Include TypeScript interfaces for ShipBobInventoryItem, ShipBobOrder, ShipBobShipment. Create a .env with SHIPBOB_ACCESS_TOKEN placeholder.
Paste this in Bolt.new chat
1// lib/shipbob.ts2const BASE_URL = 'https://api.shipbob.com/2.0';34async function shipbobFetch<T>(path: string, options: RequestInit = {}): Promise<T> {5 const token = process.env.SHIPBOB_ACCESS_TOKEN;6 if (!token) throw new Error('SHIPBOB_ACCESS_TOKEN is not configured');78 const response = await fetch(`${BASE_URL}${path}`, {9 ...options,10 headers: {11 Authorization: `Bearer ${token}`,12 'Content-Type': 'application/json',13 ...options.headers,14 },15 });1617 if (!response.ok) {18 const error = await response.json().catch(() => ({ message: response.statusText }));19 throw new Error(`ShipBob API ${response.status}: ${error.message || JSON.stringify(error)}`);20 }2122 return response.json() as Promise<T>;23}2425export interface ShipBobInventoryItem {26 id: number;27 name: string;28 is_active: boolean;29 total_fulfillable_quantity: number;30 total_onhand_quantity: number;31 total_committed_quantity: number;32 fulfillable_quantity_by_fulfillment_center: Array<{33 id: number;34 name: string;35 fulfillable_quantity: number;36 }>;37}3839export const shipbob = {40 getInventory: (page = 1, limit = 100) =>41 shipbobFetch<ShipBobInventoryItem[]>(`/inventory?Page=${page}&Limit=${limit}`),4243 createOrder: (orderData: object) =>44 shipbobFetch('/order', { method: 'POST', body: JSON.stringify(orderData) }),4546 getOrder: (id: string) => shipbobFetch(`/order/${id}`),4748 listShipments: (orderId?: string) =>49 shipbobFetch(`/shipment${orderId ? `?OrderId=${orderId}` : ''}`),50};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.
Build Inventory and Order API Routes
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.
Create Next.js API routes for ShipBob. First, /api/shipbob/inventory (GET) that calls shipbob.getInventory() and returns the inventory list with total quantities and per-fulfillment-center breakdown. Second, /api/shipbob/orders (POST) that accepts {referenceId, recipient: {name, address1, city, state, zipCode, country}, products: [{productId, quantity}]} and calls shipbob.createOrder() with ShipBob's required schema. Return the created order id, status, and estimated ship date. Include proper error handling with descriptive messages.
Paste this in Bolt.new chat
1// app/api/shipbob/orders/route.ts2import { NextResponse } from 'next/server';3import { shipbob } from '@/lib/shipbob';45export async function POST(request: Request) {6 try {7 const body = await request.json();8 const { referenceId, recipient, products } = body;910 if (!recipient || !products || products.length === 0) {11 return NextResponse.json(12 { error: 'recipient and products are required' },13 { status: 400 }14 );15 }1617 const shipbobOrder = await shipbob.createOrder({18 reference_id: referenceId,19 order_date: new Date().toISOString(),20 shipping_method: 'Standard',21 recipient: {22 name: recipient.name,23 address: {24 address1: recipient.address1,25 city: recipient.city,26 state: recipient.state,27 zip_code: recipient.zipCode,28 country: recipient.country || 'US',29 },30 },31 products: products.map((p: { productId: number; quantity: number }) => ({32 id: p.productId,33 quantity: p.quantity,34 reference_id: referenceId,35 })),36 });3738 return NextResponse.json(shipbobOrder, { status: 201 });39 } catch (error: unknown) {40 const e = error as { message: string };41 return NextResponse.json({ error: e.message }, { status: 500 });42 }43}Pro tip: ShipBob requires shipping_method to match one of the methods configured for your account. Common values are 'Standard', 'Expedited', and '2-Day'. Check your ShipBob account's shipping methods to use the correct string.
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.
Build the Fulfillment Dashboard UI
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).
Build a fulfillment dashboard at /dashboard/fulfillment with two tabs: Inventory and Orders. Inventory tab: fetch from /api/shipbob/inventory, show a table with product name, total fulfillable qty (highlighted red if < 10), total committed qty, and a collapsible row showing fulfillment center breakdown. Orders tab: create /api/shipbob/orders (GET) to list recent orders with status badges (Processing=yellow, Fulfilled=green, Exception=red). Add a refresh button and auto-refresh every 5 minutes. Show loading skeletons while fetching.
Paste this in Bolt.new chat
Pro tip: Add a manual 'Refresh' button to the inventory dashboard in addition to auto-refresh. Warehouse managers often want to trigger an immediate refresh after receiving a new shipment of inventory into ShipBob without waiting for the auto-refresh interval.
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.
Deploy and Configure ShipBob Webhooks
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.
Create a ShipBob webhook handler at /api/shipbob/webhook (POST). Parse the incoming ShipBob webhook payload which has a topic (like 'order_shipped') and data object. Handle these events: 'order_shipped' (update order status in database, store tracking number), 'shipment_delivered' (mark order as delivered), 'inventory_quantity_changed' (log the inventory update). Return 200 OK immediately for all events. Validate that the request contains the expected ShipBob webhook headers. Log the event type and relevant IDs.
Paste this in Bolt.new chat
1// app/api/shipbob/webhook/route.ts2import { NextResponse } from 'next/server';34interface ShipBobWebhookPayload {5 topic: string;6 data: Record<string, unknown>;7}89export async function POST(request: Request) {10 try {11 const payload = await request.json() as ShipBobWebhookPayload;12 const { topic, data } = payload;1314 console.log(`ShipBob webhook received: ${topic}`, JSON.stringify(data).slice(0, 200));1516 switch (topic) {17 case 'order_shipped':18 // Update order status and store tracking number19 // await updateOrderStatus(data.order_id, 'shipped', data.tracking_number);20 break;21 case 'shipment_delivered':22 // Mark order as delivered23 // await updateOrderStatus(data.order_id, 'delivered');24 break;25 case 'inventory_quantity_changed':26 // Log inventory change for dashboard refresh27 console.log('Inventory change for product:', data.product_id);28 break;29 default:30 console.log(`Unhandled ShipBob event: ${topic}`);31 }3233 // Always return 200 quickly — ShipBob retries if it doesn't receive 20034 return NextResponse.json({ received: true });35 } catch (error: unknown) {36 const e = error as { message: string };37 console.error('Webhook processing error:', e.message);38 // Still return 200 to prevent ShipBob retries for parse errors39 return NextResponse.json({ received: true });40 }41}Pro tip: ShipBob retries webhook delivery 3 times over 24 hours if it doesn't receive a 200 response. Always return 200 from your webhook handler immediately, even if processing fails. Handle business logic errors gracefully without letting them bubble up as 500 responses.
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.
Common 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.
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.
Copy this prompt to try it in Bolt.new
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.
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.
Copy this prompt to try it in Bolt.new
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.
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.
Copy this prompt to try it in Bolt.new
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.
1// Fetch ShipBob product IDs before creating orders:2const products = await shipbobFetch<{ id: number; name: string; reference_id: string }[]>('/product');3// Map from your SKU to ShipBob's integer ID:4const 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.
1// Explicitly request active inventory:2const inventory = await shipbobFetch('/inventory?Page=1&Limit=250&IsActive=true');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
Alternatives
ShipStation is a multi-carrier shipping software focused on order management and rate shopping across carriers rather than warehouse fulfillment, better for merchants shipping from their own location.
AfterShip specializes in shipment tracking and post-purchase notifications across 900+ carriers, useful when you need tracking visibility across multiple fulfillment providers.
Printful is a print-on-demand fulfillment service that handles production and shipping of custom-printed products, the right choice when you need on-demand manufacturing rather than warehoused inventory.
The FedEx API provides direct carrier integration for rate shopping, label generation, and tracking without using a fulfillment intermediary, suitable for merchants who manage their own warehouse.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation