Ship It: Building our Logistics Platform

Whatnot Engineering
Whatnot Engineering
8 min readSep 6, 2022

Tyler Laredo| Software Engineer, Payments & Logistics

3… 2… 1… congrats! You won the auction! Your card gets charged and your prized possession arrives at your front door a few days later.

Since day one, convenient, transparently-priced shipping has been an integral part of the Whatnot experience. When it comes to shipping and fulfillment, we have two guiding principles:

  1. Provide fair, predictable pricing. When you join a livestream, you should know what you’ll pay for shipping. Pricing should also be fair — lighter items like Pokémon cards should cost less to ship than heavier items like sneakers.
  2. Provide an incredibly simple user experience. As a buyer, when you purchase an item, you should get charged for shipping and have it arrive at your front door. As a seller, you pack your items, slap on a prepaid shipping label, and drop off the package at your local post office. Nothing more.

As our business grows into new markets and categories, our systems powering the shipping experience also need to scale and evolve. We have complex business logic for price selection and label generation to uphold these principles. In this post, I will share more about how we rebuilt our logistics systems to set ourselves up for the future.

An overview of our new Logistics Platform
An overview of our new Logistics Platform

Whatnot Shipping: A History

To understand the need to rebuild our logistics platform, it helps to have some historical context on shipping at Whatnot:

  • December 2019 — Whatnot launched as an asynchronous marketplace for Funko Pops. We had simple pricing from the start: buyers and sellers would both pay a hard-coded flat-rate shipping fee on every sale.
  • July 2020 — we expanded into livestream with the same flat-rate pricing model.
  • May 2021 — to distribute shipping costs more fairly, we introduced variable flat-rate costs for the first item purchased in a livestream. This meant that we could charge buyers less in shipping costs for lightweight items (e.g. Pokémon cards) than heavier items (e.g. sneakers). We also introduced $1 for every additional item purchased in that same livestream (we call this ‘incremental shipping’). Incremental shipping also optimized costs, since buyers and sellers would only have to pay for a single package instead of multiple.
  • March 2022 — we expanded to Canada, which required us to maintain hardcoded sets of flat-rate prices for local shipments in US, Canada, and international shipments either way.
  • May 2022 — to make costs even more fair, we started charging variable costs for incremental shipping. This meant that shipping could be even more affordable for many buyers (e.g. 25 cents for an additional sports card instead of $1). In contrast, other buyers would pay more in incremental shipping for heavier items (e.g. video game consoles).

As our product quickly expanded into new categories and markets, our systems became more brittle for determining rates and shipping prices:

  • We were maintaining hardcoded lists of shipping prices with ugly conditional logic to pick the correct price.
  • We had inconsistent and over-complicated logic for selecting the best-value shipping service for label generation.
  • We didn’t have much support for testing changes in a sane way. We wanted the ability to shadow and validate changes using real production traffic.

It was time for a change.

Introducing Logistics Platform

With market expansion into Canada and highly requested options like Media Mail, we realized that we needed to consolidate and redesign our systems to handle additional use cases more modularly. For example, Media Mail® is a USPS shipping service that’s only available for a subset of our products (books, CDs and DVDs) but would be much cheaper to buyers of those products. Adding support for Media Mail in our old hardcoded world would have added more random complexity to an overloaded system.

We identified three components to rebuild from the ground up. These together make up the foundation for our Logistics Platform:

  1. Fixed Tier Pricing Engine
  2. Rate Selection Engine
  3. Customs Declaration Engine
Logistics Platform Core Components
Logistics Platform Core Components

Fixed Tier Pricing Engine

To provide fair, predictable pricing, we charge most users a flat rate per category, regardless of whether the cost of the shipping label varies from order to order. Before the introduction of the Logistics Platform, we had multiple dictionaries of weight ranges and prices for each origin/destination combination, along with a plethora of if/else statements for determining price tiers.

Legacy price selection logic
Legacy price selection logic (pseudocode)

At Whatnot, we move uncomfortably fast to ship new features, especially if we want to get feedback quickly for solving user pain points. This setup worked when we were maintaining fixed prices for local US shipments but broke down when we encountered new use cases:

  • Expansion into new markets, each with new fixed price tiers per origin/destination combination. If we wanted to add more markets to our setup, managing all the permutations of origin/destination countries would quickly become unmanageable. For example, when expanding to Canada, we were suddenly managing fixed-tier pricing for US→US, US→CA, CA→US, and CA→CA shipments.
  • Experimenting with new price tiers for an existing origin/destination.

Our rigid system of conditional statements became unwieldy, so we moved to a more modular approach. We implemented a basic rule engine, where we ran through an ordered list of price tier rules and evaluated their predicates. For the highest priority rule whose predicate returns true, we select that price:

Fixed Tier Pricing Engine

This new setup allowed us to:

  • Improve testing and readability by reducing the number of if/else statements proliferated through the codebase.
  • Easily add fixed rates for new markets.
  • Easily experiment and shadow changes for existing price tiers.

Rate Selection Engine

Whatnot integrates with Shippo, a third-party service that allows us to connect with shipping carriers around the globe easily. We use Shippo to fetch shipping rates, make customs declarations on cross-border shipments, track packages, and generate shipping labels. Their API is a black box — you provide the package size/weight and origin/destination, and they spit out a list of rates and carriers you can ship with. Ideally, we will choose the cheapest rate.

However, the cheapest rate is not necessarily the best-value rate. For example, Canada Post’s cheapest CA → US service is Small Packet™–USA. However, this doesn’t currently support tracking, which is a hard requirement for our business because we use tracking to determine sellers’ payout schedules and provide visibility to buyers and our support staff. To determine the best-value rate, we remove some rates that don’t meet our criteria for shipping standards before selecting the cheapest rate of the remaining rates.

Before the introduction of the Logistics Platform, this rate filtering and selection was inconsistently sprinkled throughout the codebase. This was fine for quickly launching MVPs, but we needed a more extensible solution before we could consider expanding to new markets or experimenting with new shipping options. We implemented a similar rule-engine approach that we used for fixed-tier pricing:

Rate Selection

Customs Declaration Engine

All cross-border shipments leaving the US or Canada need a customs declaration. This may seem pretty simple — if the country codes differ, then make a customs declaration.

However, there are several edge cases that a customs declaration engine has to consider. Some countries require different metadata, while other countries have trade agreements that relax constraints on customs declarations (e.g. European Union countries). Even in the US, customs declarations are required for shipments to US military bases, despite those addresses appearing to share the same ‘country’. With such a complex decision tree of customs requirements that could grow as we continue to expand, this warranted its own rule engine, with a similar design to our Fixed Tier Pricing and Rate Selection engines.

Implementation & Rollout

How did we roll out the Logistics Platform to replace our legacy systems? Launching each engine involved a five-step process:

1.Consolidate logic to a central location: As mentioned above, our rate selection logic was inconsistently sprinkled throughout the code base. We needed to centralize our legacy logic to enable a seamless rollout to our new business logic.

2. Implement the new engine: We re-analyzed the product requirements and built the engine from scratch with a comprehensive test suite.

3. Shadow new engine (1st feature flag): For example, our setup for shadowing the Rate Selection Engine looked something like this:

Shadow setup of Rate Selection Engine (pseudocode)
Shadow setup of Rate Selection Engine (pseudocode)

We slowly turned on the first feature flag (run_new_rate_selection_engine), then monitored any mismatches between the legacy rate selection logic and the new rate selection engine. We then either fixed any bugs in the new engine, or documented legacy bugs that the new engine fixed.

4. Roll out the new engine (2nd feature flag): After shadow analysis was complete, we slowly ramped up the second feature flag (use_new_rate_selection_engine) to use the selected rate from the new engine.

5. Delete legacy code.

Looking Ahead

The Logistics Platform allows us to test and launch new shipping options and markets confidently in a matter of days without introducing complexity to our codebases. It has also improved our observability, code maintainability, and testability. However, the Logistics Platform is still evolving. There are so many new problems and features we’re excited to tackle with this new platform, such as:

  • Accounting for user preferences (shipping speed, shipping carriers).
  • Launching new product categories with different shipping requirements.
  • Launching into new markets with different regulatory complexities.

In all, we built these complex systems to uphold our promise to users: to provide fair, predictable pricing and an incredibly simple user experience.

If you enjoy solving complex problems and building elegant user experiences, let us know! We’re hiring and would love to hear from you.

Special thanks to Aidan Church, Seamus O Ceanainn, and Radek Morytko.

--

--