DEV Community

Cover image for Lessons from Shipping a Multi-Vendor Marketplace to Production
Arbythecoder
Arbythecoder

Posted on

Lessons from Shipping a Multi-Vendor Marketplace to Production

I recently shipped a multi-vendor marketplace platform with vendor subdomains, payment processing, and file uploads. During development and deployment, I ran into several issues that weren't obvious from tutorials or documentation. Here's what happened and how I solved them.

The Setup

Frontend on Vercel, backend on Fly.io, vendors get their own subdomains. The architecture worked well, but production revealed edge cases I hadn't considered during local development.

1. CORS Configuration for Wildcard Subdomains

What Happened:

Vendor subdomains like vendorname.example.com were getting blocked when making API requests:

Access to fetch at 'https://api-backend.fly.dev/api/vendor/products' from origin 'https://vendorname.example.com' has been blocked by CORS policy.
Enter fullscreen mode Exit fullscreen mode

The issue was that my initial CORS setup only whitelisted the main domain, not the subdomain pattern.

The Fix:

Updated backend CORS to whitelist both the main domain and wildcard subdomains:

https://example.com
https://*.example.com
Enter fullscreen mode Exit fullscreen mode

Made sure credentials: true was set along with the necessary methods and headers.

Takeaway:

Dynamic subdomains need explicit CORS patterns. This is common in multi-tenant applications but easy to miss until you test in production with real subdomains.

2. Data Structure Mismatch in Image Uploads

What Happened:

The backend schema expected images: [String], but during testing I noticed the frontend was sending:

[{ url: null, publicId: undefined, isPrimary: true }]
Enter fullscreen mode Exit fullscreen mode

This caused MongoDB CastErrors that broke product creation.

The Fix:

Normalized the data on the frontend before sending:

submitData.images = imageUrls.map(img => img.url).filter(Boolean);
Enter fullscreen mode Exit fullscreen mode

The backend image upload endpoint now returns string[] for database insertion while keeping richer metadata for UI state management.

Takeaway:

When building APIs, I've learned to validate data shape at both ends. Frontend state often needs more metadata than the database requires, so mapping before submission prevents type errors.

3. API Response Inconsistency

What Happened:

After creating vendor profiles, the database showed correct values but the API response was returning hardcoded defaults:

{
  "canReceiveOrders": false,
  "isPublic": false
}
Enter fullscreen mode Exit fullscreen mode

This created confusion in the UI, showing stores as inactive when they were actually live.

The Fix:

Changed the response to reflect actual database state:

res.status(201).json({
  success: true,
  message: 'Vendor profile created successfully! Your store is now live.',
  data: {
    vendor,
    approvalStatus: vendor.approvalStatus,
    canReceiveOrders: vendor.isVerified,
    isPublic: vendor.isPublic
  }
});
Enter fullscreen mode Exit fullscreen mode

Takeaway:

Response payloads should always reflect database state, not assumptions. This seems obvious but during rapid iteration it's easy to return static values and forget to update them.

4. Payment Webhook Security Gap

What Happened:

While reviewing the payment flow, I realized the webhook endpoint was accepting POST requests without verifying they actually came from Stripe. This is a common attack vector.

The Fix:

Implemented signature verification:

const sig = req.headers['stripe-signature'];
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
Enter fullscreen mode Exit fullscreen mode

Takeaway:

Payment webhooks are critical infrastructure. Signature verification should be implemented from day one, not added later. I've made it part of my initial setup checklist now.

5. Missing Authorization on Product Updates

What Happened:

The route /products/:id/stock verified that a vendor was logged in, but didn't check if they owned the product. This meant any vendor could potentially modify another vendor's inventory.

The Fix:

Added ownership verification middleware:

router.patch('/products/:id/stock', protect, authorize('vendor'), attachVendor, validateMongoId('id'), updateStock);
Enter fullscreen mode Exit fullscreen mode

The attachVendor middleware ensures requests only succeed if the product belongs to the authenticated vendor.

Takeaway:

Authentication confirms who you are, authorization confirms what you can access. For multi-tenant systems, every resource route needs ownership checks. IDOR vulnerabilities are common when building quickly.


6. Deployment Cache Issues on Vercel

What Happened:

After pushing updates, the production site wasn't reflecting new code. Features that worked locally, including the fixes above, weren't showing up.

The Fix:

Forced a clean deployment:

vercel --prod --force
Enter fullscreen mode Exit fullscreen mode

This bypassed caches and deployed fresh. Everything worked after that.

Takeaway:

Platform-specific quirks exist. Vercel, Netlify, and similar services cache aggressively. When behavior doesn't match your local environment, a forced rebuild often resolves it.

Quick Reference

Issue Cause Solution Why It Matters
CORS blocking subdomains Wildcard pattern not configured Whitelist https://*.example.com Common in multi-tenant apps
Image upload type error Frontend/backend schema mismatch Normalize data before sending Prevents runtime errors
Response inconsistency Hardcoded response values Return actual DB state Keeps UI in sync
Vendor IDOR vulnerability Missing ownership check Add authorization middleware Critical for multi-tenant security
Webhook security gap No signature verification Verify Stripe signatures Prevents payment fraud
Stale deployments Build cache Force clean deploy Platform-specific behavior

What I'd Do Differently Next Time

  • Set up CORS patterns early when working with subdomains, not after deployment.
  • Add ownership middleware to all resource routes from the start, not during security review.
  • Implement webhook verification before testing payment flows.
  • Document platform quirks as I encounter them for faster debugging later.

These weren't beginner mistakes; they're the kind of issues that surface when shipping real applications with multiple user roles, payment processing, and complex domain setups. Documenting them helps me build faster next time and hopefully helps other developers avoid the same debugging sessions.



Enter fullscreen mode Exit fullscreen mode

Top comments (0)