# How to Integrate Bolt.new with Shift4Shop

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

## TL;DR

Connect Bolt.new to Shift4Shop (formerly 3dcart) using its REST API with three credentials: your store's SecureURL, a Public Token, and a Private Key. All API calls must go through a Next.js API route to keep the private key server-side. Build custom storefronts, order dashboards, or product management UIs on top of Shift4Shop's hosted catalog. Outbound REST calls work in Bolt's WebContainer during development.

## Build a Custom Shift4Shop Storefront or Admin Dashboard with Bolt.new

Shift4Shop stands out in the e-commerce platform market for a unique reason: it offers a completely free plan with no monthly fees, no transaction fees, and no limits on products or bandwidth — the only condition is using Shift4 Payments as your payment processor. This makes it an attractive backend for builders who want a fully hosted e-commerce engine without the $29/month Shopify Basic subscription.

The Shift4Shop REST API gives you programmatic access to everything in your store: products, categories, orders, customers, inventory, shipping options, and coupons. This enables two powerful use cases with Bolt.new. First, you can build a completely custom storefront — your own React UI pulling product and catalog data from Shift4Shop, with Shift4Shop handling the cart, checkout, and payment processing in the background. Second, you can build internal admin dashboards for order fulfillment, inventory management, or sales reporting that your team uses alongside or instead of the default Shift4Shop admin interface.

Authentication uses three values: the store's SecureURL (the base URL for API calls), a Public Token (can be used for read-only catalog calls in some contexts), and a Private Key (required for all order and customer data). The Private Key must never leave your server — store it in .env and access it only from Next.js API routes. During Bolt.new development, all outbound calls to Shift4Shop work in the WebContainer. If you need Shift4Shop to push order or inventory events to your app via webhooks, you must deploy to Netlify or Vercel first and register a public URL.

## Before you start

- A Shift4Shop store — sign up free at shift4shop.com (free plan available with Shift4 Payments)
- API credentials from Shift4Shop admin: Settings → API → Generate API Access Token (gives you SecureURL, Public Token, and Private Key)
- A Bolt.new account with a new Next.js project open
- Products already added to your Shift4Shop store to test catalog and inventory API calls
- A Netlify account for deployment if you need Shift4Shop order notification webhooks

## Step-by-step guide

### 1. Generate Shift4Shop API credentials and set up authentication

Shift4Shop uses a three-part authentication system that you include as HTTP request headers on every API call. Understanding each credential's purpose helps you use them correctly and securely.

To generate credentials: log into your Shift4Shop admin panel, go to Settings (gear icon) → API → REST API. If you haven't enabled the REST API, toggle it on. Click Generate to create a new API access token. Shift4Shop shows you three values:

1. SecureURL — This is the base URL for all your API calls. It looks like `https://api.shift4shop.com/v3/{your-store-id}`. Every API request goes to this URL plus the endpoint path (e.g., `/Products`, `/Orders`).

2. PublicToken — A token that can be used for read-only catalog operations. Some integrations use this in client-side code for public data like product listings. However, for consistency and security, this guide uses it server-side only in API routes.

3. PrivateKey — This is the sensitive credential that authenticates write operations and access to order/customer data. It must never appear in client-side code, NEXT_PUBLIC_ environment variables, or be committed to Git.

All three headers are sent together on every request: `SecureURL`, `PublicToken`, and `PrivateKey` as HTTP headers. The Shift4Shop API is RESTful with standard HTTP verbs (GET for reads, POST for creates, PUT for updates, DELETE for deletions) and returns JSON.

Add all three values to your .env file in Bolt. The Shift4Shop REST API accepts HTTPS requests, and these outbound calls work correctly in Bolt's WebContainer during development.

```
// lib/shift4shop.ts
const getCredentials = () => {
  const secureUrl = process.env.SHIFT4SHOP_SECURE_URL;
  const publicToken = process.env.SHIFT4SHOP_PUBLIC_TOKEN;
  const privateKey = process.env.SHIFT4SHOP_PRIVATE_KEY;

  if (!secureUrl || !publicToken || !privateKey) {
    throw new Error(
      'Missing Shift4Shop credentials. Set SHIFT4SHOP_SECURE_URL, ' +
      'SHIFT4SHOP_PUBLIC_TOKEN, and SHIFT4SHOP_PRIVATE_KEY in .env'
    );
  }
  return { secureUrl, publicToken, privateKey };
};

export async function shift4shopFetch<T = unknown>(
  endpoint: string,
  init: RequestInit = {}
): Promise<T> {
  const { secureUrl, publicToken, privateKey } = getCredentials();
  const url = `${secureUrl}${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;

  const response = await fetch(url, {
    ...init,
    headers: {
      SecureURL: secureUrl,
      PublicToken: publicToken,
      PrivateKey: privateKey,
      'Content-Type': 'application/json',
      Accept: 'application/json',
      ...init.headers,
    },
  });

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

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

**Expected result:** A configured Shift4Shop API helper in lib/shift4shop.ts with all three credentials stored in .env, ready to use in API routes.

### 2. Fetch products and build a catalog API route

The Shift4Shop Products endpoint (`GET /Products`) returns your store's full product catalog with rich data including name, description, SKU, price, sale price, stock levels, categories, images, and custom fields. This is the foundation for any custom storefront or catalog integration.

The Products endpoint supports several useful query parameters for pagination and filtering. `countonly=1` returns just the count of matching products — useful for determining how many pages to show. `offset` and `limit` control pagination (max 200 per request). `categoryid` filters by category. `hide=false` returns only active/visible products. For custom storefronts, you typically want `hide=false` to match what your live store shows.

Product images are returned in the `Images` array — the first image is typically the primary product image. Each image object includes `Url` (the full CDN URL) and `AltText`. Shift4Shop hosts these images on their CDN, so you can use the URLs directly in your React components without any additional asset handling.

Inventory data is included in the product response as `Stock`, `StockAlert` (the low stock threshold), and `InventoryControl` (whether stock tracking is enabled for this product). This makes it easy to build inventory-aware UIs — showing 'Out of Stock' badges, filtering to in-stock products only, or building the inventory alert dashboard described in the use cases.

For large catalogs, implement pagination in your API route using `offset` and `limit` parameters. A catalog with 500+ products should never be fetched in one call — load the first page immediately and implement infinite scroll or 'load more' pagination in your React component.

```
// app/api/shift4shop/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { shift4shopFetch } from '@/lib/shift4shop';

interface Shift4ShopProduct {
  CatalogID: number;
  SKU: string;
  Name: string;
  Price: number;
  SalePrice: number;
  Stock: number;
  Hide: boolean;
  Images: Array<{ Url: string; AltText: string }>;
  CategoryID: number;
}

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const limit = Math.min(Number(searchParams.get('limit') ?? 20), 200);
  const offset = Number(searchParams.get('offset') ?? 0);
  const categoryId = searchParams.get('categoryId');
  const inStockOnly = searchParams.get('inStockOnly') === 'true';

  const params = new URLSearchParams({
    limit: limit.toString(),
    offset: offset.toString(),
    hide: 'false',
  });
  if (categoryId) params.set('categoryid', categoryId);

  const products = await shift4shopFetch<Shift4ShopProduct[]>(
    `/Products?${params.toString()}`
  );

  const normalized = products
    .filter(p => !inStockOnly || p.Stock > 0)
    .map(p => ({
      id: p.CatalogID,
      sku: p.SKU,
      name: p.Name,
      price: p.Price,
      salePrice: p.SalePrice > 0 && p.SalePrice < p.Price ? p.SalePrice : null,
      stock: p.Stock,
      isInStock: p.Stock > 0,
      thumbnail: p.Images?.[0]?.Url ?? null,
    }));

  return NextResponse.json({ products: normalized, offset, limit });
}
```

**Expected result:** A products API route that returns normalized product data from Shift4Shop, and a React component displaying the catalog in a responsive grid.

### 3. Build an order management API route and dashboard

The Shift4Shop Orders endpoint (`GET /Orders`) returns all orders with full details including customer information, line items, shipping method, order status, and payment status. This is the data your fulfillment team needs to process and ship orders.

Shift4Shop uses numeric status codes for order status. The key ones are: 1 (New), 2 (Processing), 3 (Awaiting Fulfillment), 4 (Shipped), 5 (Cancelled), 6 (Declined), 7 (Awaiting Payment), 8 (Backordered), 9 (Awaiting Shipment), 12 (Refunded). For a fulfillment dashboard, you typically filter to status 1 (New), 2 (Processing), and 9 (Awaiting Shipment).

Orders include the `OrderItems` array with each line item's product name, SKU, quantity, unit price, and the order subtotal, shipping cost, tax amount, and total. The `ShipmentList` array shows any shipments created for the order with tracking numbers.

Customer data is embedded in the order: `BillingFirstName`, `BillingLastName`, `BillingEmail`, and the `ShipFirstName`, `ShipLastName`, `ShipAddress` fields for the delivery address. All the information your team needs to print a packing slip or verify an address is in one API call.

For the dashboard, a well-organized order table dramatically speeds up fulfillment workflows. Show order number, customer name, destination city/state, item count, order total, and status badge. Make the order number clickable to expand a drawer showing full line items and customer details. Add a status filter dropdown so the team can focus on orders needing attention.

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

const STATUS_LABELS: Record<number, string> = {
  1: 'New',
  2: 'Processing',
  3: 'Awaiting Fulfillment',
  4: 'Shipped',
  5: 'Cancelled',
  6: 'Declined',
  7: 'Awaiting Payment',
  8: 'Backordered',
  9: 'Awaiting Shipment',
  12: 'Refunded',
};

interface Shift4ShopOrder {
  InvoiceNumber: number;
  InvoiceDate: string;
  BillingFirstName: string;
  BillingLastName: string;
  BillingEmail: string;
  OrderStatus: number;
  OrderItems: unknown[];
  OrderAmount: number;
  ShipCity: string;
  ShipState: string;
  ShipCountry: string;
}

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const status = searchParams.get('status');
  const limit = searchParams.get('limit') ?? '50';
  const offset = searchParams.get('offset') ?? '0';

  const params = new URLSearchParams({ limit, offset });
  if (status) params.set('StatusID', status);

  const orders = await shift4shopFetch<Shift4ShopOrder[]>(
    `/Orders?${params.toString()}`
  );

  const normalized = orders.map(o => ({
    id: o.InvoiceNumber,
    orderDate: o.InvoiceDate,
    customerName: `${o.BillingFirstName} ${o.BillingLastName}`.trim(),
    customerEmail: o.BillingEmail,
    status: o.OrderStatus,
    statusLabel: STATUS_LABELS[o.OrderStatus] ?? `Status ${o.OrderStatus}`,
    itemCount: o.OrderItems?.length ?? 0,
    total: o.OrderAmount,
    shippingAddress: {
      city: o.ShipCity,
      state: o.ShipState,
      country: o.ShipCountry,
    },
  }));

  return NextResponse.json({ orders: normalized });
}
```

**Expected result:** An orders API route returning normalized Shift4Shop order data with readable status labels, and a dashboard component for filtering and viewing orders.

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

Outbound Shift4Shop API calls work flawlessly during Bolt.new development in the WebContainer — fetching products, orders, and inventory all function correctly in the preview environment. The only functionality that requires deployment is Shift4Shop's real-time notification system, which uses webhooks to push order and shipment events to your app.

Shift4Shop offers webhook notifications (called 'Callbacks' in their interface) for events including new orders, order status changes, and inventory updates. These are POST requests sent from Shift4Shop servers to your endpoint URL. Since Bolt.new's WebContainer has no persistent public URL and cannot receive incoming HTTP connections, these notifications cannot reach your app during development.

To deploy and configure webhooks: click Deploy in Bolt.new and connect your Netlify account. After deployment succeeds, go to your Netlify dashboard → Site Configuration → Environment Variables and add all three Shift4Shop credentials: `SHIFT4SHOP_SECURE_URL`, `SHIFT4SHOP_PUBLIC_TOKEN`, and `SHIFT4SHOP_PRIVATE_KEY`. Trigger a redeploy to apply the variables.

For Shift4Shop webhooks: log into your Shift4Shop admin, go to Settings → API → Webhooks (or Callbacks depending on your version). Add a new webhook pointing to `https://your-app.netlify.app/api/shift4shop/webhook` for Order events. Shift4Shop sends a POST with the order number when triggered — your handler then calls back to the Shift4Shop API using the order number to fetch full details. This two-step pattern (notification → fetch) is common in e-commerce platform webhooks.

A practical use case for the webhook: send an order confirmation email via SendGrid when Shift4Shop posts a new order notification. The webhook receives the order ID, your handler fetches the full order details from Shift4Shop, and then sends a branded email to the customer.

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

// IMPORTANT: Shift4Shop webhooks require a publicly deployed URL.
// During Bolt.new WebContainer development, incoming webhook callbacks
// cannot be received. Deploy to Netlify first, then configure your
// webhook URL in Shift4Shop Admin → Settings → API → Webhooks.
export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const orderId = body.InvoiceNumber ?? body.orderId;

    if (orderId) {
      // Fetch full order details using the order ID from the notification
      const orders = await shift4shopFetch<unknown[]>(
        `/Orders?InvoiceNumber=${orderId}`
      );
      const order = Array.isArray(orders) ? orders[0] : orders;

      if (order) {
        const o = order as Record<string, unknown>;
        console.log(
          `New Shift4Shop order #${orderId}: ` +
          `${o.BillingFirstName} ${o.BillingLastName} — ` +
          `$${o.OrderAmount}`
        );
        // TODO: Send order confirmation email, update internal database, etc.
      }
    }
  } catch (error) {
    // Log but don't expose — always return 200 to acknowledge receipt
    console.error('Shift4Shop webhook error:', error);
  }

  // Always return 200 to prevent Shift4Shop from retrying
  return NextResponse.json({ received: true });
}
```

**Expected result:** A deployed Netlify app with a webhook handler ready to receive Shift4Shop order notifications, enabling real-time order processing workflows.

## Best practices

- Store all three Shift4Shop credentials (SecureURL, PublicToken, PrivateKey) as server-side environment variables — never expose any of them in NEXT_PUBLIC_ variables or client-side code
- Create a centralized lib/shift4shop.ts helper that handles authentication headers for all API calls, rather than repeating the auth logic in every API route
- Implement pagination for large catalogs — the Products endpoint allows up to 200 items per request, but stores with thousands of products should use offset-based pagination
- Add response caching for product catalog data since product listings change infrequently — cache for 60-300 seconds to reduce API calls and improve page load times
- Use Shift4Shop's status code constants in a shared mapping object rather than hardcoding numeric status values directly in components
- Always return HTTP 200 from webhook handlers even if processing fails — Shift4Shop retries failed callbacks which can cause duplicate order processing
- Test your API routes against the Shift4Shop API from Bolt's WebContainer preview before deploying — all outbound calls work in development and catching errors early saves time
- For headless storefront use cases, use the Public Token for read-only product/category queries and reserve the Private Key usage for order and customer data API routes

## Use cases

### Custom Product Catalog Page

Build a visually rich product catalog using your own React components and Tailwind CSS, pulling live product data from Shift4Shop. Display products with custom filtering, sorting, and layout that matches your brand — not the default Shift4Shop theme.

Prompt example:

```
Create a product catalog page for my Shift4Shop store. Build an API route at app/api/shift4shop/products/route.ts that fetches all products from the Shift4Shop REST API using SecureURL from SHIFT4SHOP_SECURE_URL, PublicToken from SHIFT4SHOP_PUBLIC_TOKEN, and PrivateKey from SHIFT4SHOP_PRIVATE_KEY as request headers. Return an array of products with id, name, price, thumbnail, and stock. Create a React ProductCatalog component that fetches from this route and displays products in a responsive grid with search and category filter.
```

### Order Management Dashboard

Create an internal order management view that shows recent orders, their status, customer details, and line items. Allow your fulfillment team to filter by status, search by order number, and view shipping details without logging into the Shift4Shop admin panel.

Prompt example:

```
Build an order management dashboard. Create an API route at app/api/shift4shop/orders/route.ts that fetches orders from Shift4Shop using the three auth headers (SecureURL, PublicToken, PrivateKey from process.env). Accept query params for status filter (new, processing, shipped, cancelled) and limit (default 50). Return order ID, customer name, order date, status, total, and item count. Build a React Orders component with a filterable table, showing each order's key details and a status badge with appropriate color coding.
```

### Low Stock Inventory Alert Dashboard

Build a dashboard that surfaces products with low inventory levels so your team can reorder before stockouts. Poll Shift4Shop for products with stock below a configurable threshold and display them prioritized by urgency.

Prompt example:

```
Create an inventory management page that shows low-stock products. Build an API route at app/api/shift4shop/inventory/route.ts that fetches all products from Shift4Shop and filters to those with stock below a threshold (default 10 units). Sort by stock ascending. Return product name, SKU, current stock, and price. Build a React InventoryAlerts component that shows these products in a table with stock count color-coded (red for 0-3, orange for 4-9), and a header showing the total count of low-stock items.
```

## Troubleshooting

### API returns 401 Unauthorized even with correct credentials in headers

Cause: Shift4Shop requires all three headers (SecureURL, PublicToken, PrivateKey) to be present on every request, not just the PrivateKey. A common mistake is sending only one or two headers. Another cause is using the REST API URL as the base URL but omitting it from the SecureURL header — both the URL and the header must be present.

Solution: Verify that all three headers are included in every request: SecureURL (the full API base URL), PublicToken, and PrivateKey. Check your lib/shift4shop.ts helper to confirm all three are being set. Also verify the header names are exactly as Shift4Shop expects — they are case-sensitive. Log the request headers in development to confirm they are being sent.

### Products endpoint returns an empty array even though products exist in the store

Cause: Shift4Shop hides inactive products by default in some API configurations. If products are set to 'Hide' in the admin panel, or if the API call is not filtering correctly, you may receive an empty result. The free plan also requires Shift4 Payments to be active — stores that haven't completed payment setup may have API access restrictions.

Solution: Add hide=false to your query parameters to include all active products. Check in the Shift4Shop admin that your products are not set to 'Hidden'. If you are on the free plan, verify your Shift4 Payments account is fully set up and connected — incomplete payment setup can restrict API access.

```
// Add hide=false to ensure active products are returned
const params = new URLSearchParams({ limit: '20', offset: '0', hide: 'false' });
const products = await shift4shopFetch(`/Products?${params.toString()}`);
```

### Shift4Shop webhook callbacks are not arriving in Bolt.new

Cause: Bolt.new's WebContainer does not have a persistent public URL that external services can reach. Shift4Shop's callback/webhook system sends POST requests to your registered URL, requiring a publicly accessible server. The Bolt.new preview URL is not a valid callback target.

Solution: Deploy your app to Netlify, then register the deployed URL in Shift4Shop admin Settings → API → Webhooks/Callbacks. Use your Netlify domain (e.g., https://your-app.netlify.app/api/shift4shop/webhook). This is a fundamental limitation of WebContainers — incoming HTTP connections always require a deployed environment.

### Images from Shift4Shop products show 'unconfigured host' error in Next.js Image component

Cause: Next.js requires all external image domains to be explicitly listed in next.config.js before they can be used in the next/image component. Shift4Shop hosts images on 3dcart.com CDN domains.

Solution: Add the Shift4Shop CDN domain to your next.config.js images configuration. The primary CDN domain is cdn.3dcart.com — add it to the remotePatterns or domains array.

```
// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: 'cdn.3dcart.com' },
      { protocol: 'https', hostname: '*.3dcart.com' },
    ],
  },
};
```

## Frequently asked questions

### Is Shift4Shop really free?

Yes, Shift4Shop's Free plan offers unlimited products, orders, bandwidth, and storage with no monthly subscription fees. The condition is using Shift4 Payments as your payment processor. Shift4 charges a payment processing fee (similar to Stripe or Square) but eliminates the platform subscription cost. This makes it among the most cost-effective hosted e-commerce platforms for high-volume sellers.

### Can I use Shift4Shop's API from Bolt.new during development without deploying?

Yes. All outbound API calls to Shift4Shop's REST API work in Bolt.new's WebContainer during development. You can fetch products, orders, inventory, and customer data from the preview environment. The only feature requiring deployment is Shift4Shop's webhook/callback system, which sends incoming POST requests that WebContainers cannot receive.

### What's the difference between Shift4Shop's Public Token and Private Key?

The Public Token is designed for read-only operations on public catalog data — some implementations use it client-side to fetch product listings. The Private Key provides full API access including order management, customer data, and write operations. For security, this guide treats both as server-side only credentials and never exposes either in client-side code or NEXT_PUBLIC_ environment variables.

### Can Bolt.new handle Shift4Shop's webhook callbacks?

Yes, after deployment. Write your webhook handler in Bolt.new, deploy to Netlify, and register the Netlify URL in Shift4Shop's admin panel (Settings → API → Webhooks). The WebContainer cannot receive incoming HTTP connections during development — this is a fundamental limitation of the browser-based runtime, not a Bolt.new-specific issue.

### How do I handle Shift4Shop's pagination for large product catalogs?

Use the offset and limit query parameters on the /Products endpoint. Limit is capped at 200 per request. First call /Products?countonly=1 to get the total product count, then calculate how many pages you need. Implement either traditional pagination (page number controls) or infinite scroll in your React component, loading additional pages as the user scrolls. For catalogs under 200 products, a single call with limit=200 fetches everything.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/3dcart-now-shift4shop
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/3dcart-now-shift4shop
