Skip to main content

Authentication

”Missing API key” or 401 errors

Cause: The X-API-Key header is missing or the key is invalid. Fix:
  1. Check that you’re passing the header: X-API-Key: sk_live_... (or sk_test_... for test mode)
  2. Verify the key in the Dashboard under Settings > API Keys
  3. Ensure the key hasn’t been revoked
  4. If using the SDK, check your environment variable: NEXT_PUBLIC_CRAVEUP_API_KEY
// Verify your client is initialized correctly
const storefront = createStorefrontClient({
  apiKey: process.env.NEXT_PUBLIC_CRAVEUP_API_KEY!, // must not be undefined
});

”Location mismatch” or 403 errors

Cause: The API key is scoped to a different location than the one in the request. Fix: Each API key is tied to a specific location. Use the key generated for the location you’re querying, or generate a new key for the target location.

Cart & Ordering

”Cart not found” (404)

Cause: The cart has expired or been deleted. Fix:
  • Carts expire after a period of inactivity. Start a new ordering session.
  • If you’re persisting cartId in localStorage, clear it and create a fresh session.
// Always handle cart expiration gracefully
try {
  const cart = await storefront.cart.get(locationId, cartId);
} catch (error) {
  if (error instanceof ApiError && error.status === 404) {
    // Cart expired — start a new session
    const session = await storefront.orderingSessions.start(locationId, {
      marketplaceId: locationId,
    });
    localStorage.setItem('cartId', session.cartId!);
  }
}

”Cart locked” (400)

Cause: A payment is being processed for this cart. Items can’t be modified during payment. Fix: Wait for the payment to complete or fail. If the payment fails, the cart unlocks automatically. If it succeeds, the cart transitions to COMPLETED.

”Invalid modifier selection” (400)

Cause: The selected modifiers don’t satisfy the group’s min/max rules. Fix:
  • Check the modifier group’s constraints before submitting
  • Ensure the number of selected options is between min and max
  • Verify that individual modifier item quantities don’t exceed their maxQuantity

”Location closed” (422)

Cause: The location is not accepting orders at the requested time. Fix:
  1. Fetch order times: storefront.locations.getOrderTimes(locationId)
  2. If requireScheduledOrders is true, show a time picker
  3. If no time slots are available, the location is closed — show a message

”Minimum order not met” (400)

Cause: The cart total is below the location’s minimum order amount. Fix: Display the minimum amount to the customer and prompt them to add more items. The minimum is available in the location settings.

Payments

Payment intent fails to create

Cause: Cart is missing required data (customer details, fulfillment method, or order time). Fix: Ensure all checkout steps are complete before creating a payment intent:
  1. Fulfillment method is set
  2. Order time is set (ASAP or scheduled)
  3. Customer details are validated
  4. For delivery: address is set with coordinates

Stripe Elements not rendering

Cause: Stripe.js is not loaded or the clientSecret is invalid. Fix:
  1. Verify NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is set
  2. Pass the stripeAccountId from the payment intent response when initializing Stripe
  3. Ensure the clientSecret is passed to the Elements provider
// Must use the restaurant's connected account
const stripePromise = loadStripe(
  process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
  { stripeAccount: payment.stripeAccountId } // required for Connect
);

3D Secure / SCA authentication popup not appearing

Cause: Missing return_url in the confirmPayment call. Fix: Always provide a return_url:
await stripe.confirmPayment({
  elements,
  confirmParams: {
    return_url: `${window.location.origin}/order/confirmation`,
  },
});

Fulfillment

”Delivery out of range” (422)

Cause: The customer’s address is outside the location’s delivery zone. Fix:
  1. Check distance before setting delivery: storefront.locations.getDistance(locationId, { lat, lng, unit: 'miles' })
  2. Compare against the location’s delivery radius
  3. Show a clear message if out of range

Table-side / room service not available

Cause: The fulfillment method is not enabled for the location. Fix: Check location.methodsStatus and only show enabled methods:
const merchant = await storefront.merchant.getBySlug('my-restaurant');
const methods = merchant.locations[0].methodsStatus;
// { pickup: true, table: true, delivery: false, roomService: false }

Webhooks

Webhooks not being received

Check:
  1. Your endpoint URL is correct and publicly accessible
  2. Your server returns 200 within 10 seconds
  3. The endpoint isn’t behind authentication (webhook requests don’t include session cookies)
  4. Check the delivery log in Dashboard under Settings > Webhooks for failed attempts

Signature verification failing

Cause: The CRAVEUP_WEBHOOK_SECRET doesn’t match, or the body is being parsed before verification. Fix:
  • Use the raw request body for signature verification (not the parsed JSON)
  • Ensure the secret matches the one shown in the Dashboard
// Must use raw body, not parsed JSON
const body = await request.text(); // not request.json()
const expected = createHmac('sha256', WEBHOOK_SECRET).update(body).digest('hex');

SDK

”TypeError: storefront.X is not a function”

Cause: Incorrect SDK version or wrong import. Fix:
  1. Update to the latest SDK: pnpm add @craveup/storefront-sdk@latest
  2. Verify the import: import { createStorefrontClient } from '@craveup/storefront-sdk'
  3. Check that you’re calling methods on the correct namespace (e.g., storefront.cart.get() not storefront.get())

SDK requests failing in Server Components

Cause: The SDK uses fetch internally. In Next.js Server Components, ensure your API key is available server-side. Fix: Use a server-side environment variable (without NEXT_PUBLIC_ prefix) or ensure the public variable is accessible in the server context.

Build & Deployment

Next.js build fails with SDK type errors

Fix: Ensure your tsconfig.json includes "moduleResolution": "bundler" or "node" and that the SDK is installed (not just a peer dependency).

Images not loading in production

Cause: Next.js Image Optimization requires configured domains. Fix: Add the Crave CDN domain to your next.config.ts:
const nextConfig = {
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: '*.craveup.com' },
      { protocol: 'https', hostname: 'res.cloudinary.com' },
    ],
  },
};

Still stuck?