Marketplace Multi-Seller Shipping

This guide shows how to integrate Envia into a marketplace platform where multiple sellers ship orders to buyers. Your platform handles carrier selection and label generation on behalf of each seller.

What you'll build

flowchart LR
  Order["Buyer Places Order"] --> Route["Route to Seller"] --> Rate["Get Rates"] --> Label["Generate Label"] --> Track["Track and Notify"]

Your marketplace will:

  1. Let sellers fulfill orders using carriers available at their location
  2. Show buyers accurate shipping rates from the seller's origin
  3. Generate labels on behalf of sellers after purchase
  4. Provide unified tracking across all sellers and carriers

Architecture overview

Buyer places order (items from multiple sellers)
  → Your platform identifies each seller
    → Your backend calls Envia GET /carrier?country_code={seller.country}
      → Returns available carriers for the seller's location
  → Your backend calls Envia POST /ship/rate/ (using seller's address as origin)
    → Returns rates + delivery estimates per seller
  → Buyer selects rates and pays
    → Your backend calls Envia POST /ship/generate/ (one label per seller)
      → Returns tracking number + label PDF per seller
  → You email a unified tracking page to the buyer
  → Envia webhooks notify you of delivery status changes per shipment

Setup

All code examples below use these base URLs and token. Set ENVIA_ENV=production when you are ready to go live.

// Configuration — defaults to sandbox
const ENVIA_BASE   = "https://api-test.envia.com";
const QUERIES_BASE = "https://queries-test.envia.com";
const GEOCODES_BASE = "https://geocodes.envia.com";
// Set ENVIA_TOKEN as an environment variable (see Authentication guide)
# Configuration — defaults to sandbox
ENVIA_BASE   = "https://api-test.envia.com"
QUERIES_BASE = "https://queries-test.envia.com"
GEOCODES_BASE = "https://geocodes.envia.com"
ENVIA_TOKEN  = os.environ["ENVIA_TOKEN"]

Step 1 — Discover carriers per seller location

Different sellers ship from different locations. Use the Queries API to find which carriers serve each seller's region.

async function getCarriersForSeller(seller) {
  const response = await fetch(
    QUERIES_BASE + "/carrier?country_code=" + seller.country,
    { headers: { Authorization: "Bearer " + process.env.ENVIA_TOKEN } }
  );
  return response.json();
}

Step 2 — Get rates from the seller's origin

When a buyer adds a product to their cart, fetch rates using the seller's address as origin.

async function getMarketplaceRates(seller, buyerAddress, packages) {
  var origin = {
    name: seller.businessName,
    phone: seller.phone,
    street: seller.street,
    city: seller.city,
    state: seller.state,
    country: seller.country,
    postalCode: seller.postalCode,
  };

  var carriers = seller.preferredCarriers || ["dhl", "fedex", "estafeta"];

  var ratePromises = carriers.map(function (carrier) {
    return fetch(ENVIA_BASE + "/ship/rate/", {
      method: "POST",
      headers: {
        Authorization: "Bearer " + process.env.ENVIA_TOKEN,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        origin: origin,
        destination: buyerAddress,
        packages: packages,
        shipment: { type: 1, carrier: carrier },
      }),
    }).then(function (r) { return r.json(); });
  });

  var results = await Promise.allSettled(ratePromises);
  return results
    .filter(function (r) { return r.status === "fulfilled" && r.value.data; })
    .flatMap(function (r) { return r.value.data; })
    .sort(function (a, b) { return parseFloat(a.totalPrice) - parseFloat(b.totalPrice); });
}

Step 3 — Handle multi-seller orders

When a buyer purchases from multiple sellers in one order, each seller ships their items separately. Your platform generates one label per seller.

async function fulfillMultiSellerOrder(order) {
  var sellerGroups = groupBySeller(order.items);
  var shipments = [];
  var sellerIds = Object.keys(sellerGroups);

  for (var i = 0; i < sellerIds.length; i++) {
    var sellerId = sellerIds[i];
    var items = sellerGroups[sellerId];
    var seller = await getSeller(sellerId);
    var packages = buildPackages(items);

    var label = await fetch(ENVIA_BASE + "/ship/generate/", {
      method: "POST",
      headers: {
        Authorization: "Bearer " + process.env.ENVIA_TOKEN,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        origin: seller.address,
        destination: order.shippingAddress,
        packages: packages,
        shipment: {
          type: 1,
          carrier: order.selectedCarriers[sellerId],
          service: order.selectedServices[sellerId],
        },
      }),
    }).then(function (r) { return r.json(); });

    shipments.push({
      sellerId: sellerId,
      trackingNumber: label.data[0].trackingNumber,
      labelUrl: label.data[0].label,
    });
  }

  return shipments;
}

Step 4 — Unified tracking for buyers

Register a webhook to receive tracking updates across all carriers and sellers. Present a unified tracking page to the buyer.

curl --request POST \
  --url https://queries-test.envia.com/webhooks \
  --header "Authorization: Bearer $ENVIA_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"type_id": 3, "url": "https://your-marketplace.com/webhooks/tracking", "active": 1}'

Marketplace-specific considerations

ConcernRecommendation
Who pays for shipping?Use a single Envia account for your marketplace. Charge sellers via your platform settlement process.
Seller onboardingCollect and validate seller addresses during onboarding using the Geocodes API.
Carrier preferencesLet sellers set preferred carriers. Fall back to all available carriers.
International sellersUse the International Shipping Workflow for cross-border orders.
ReturnsUse the Cancel Shipment endpoint to void labels before carrier scan. Refunds are applied automatically to your account balance.

Related pages