Shippo setup
Both ToT and Storefront use Shippo to purchase USPS labels. One Shippo account, one API token, two different ship-from configurations (ToT global / Storefront per-shop).
Why Shippo
- Cheaper USPS rates than retail (commercial Plus pricing)
- Single API for USPS, UPS, FedEx, DHL — easy to add carriers later
- Address validation included
- Tracking webhooks for buyer notifications
- No monthly fee on the free tier (pay-per-label)
1 · Sign up
- Go to goshippo.com → "Get Started"
- Sign up with the same email you use for Stripe (helps with consistency)
- Choose the Free plan — no monthly fee, just per-label cost
- Verify your email
2 · Get your API token
- Shippo dashboard → Settings → API
- You'll see two tokens:
- Test Token: starts with
shippo_test_ - Live Token: starts with
shippo_live_
- Test Token: starts with
- Copy the test token first — you'll set live later
On Railway → ToT service → Variables:
SHIPPO_API_TOKEN=shippo_test_...
On Railway → Storefront service → Variables:
SHIPPO_API_TOKEN=shippo_test_...
Same token works for both repos. They each call Shippo independently with their own ship-from address.
Labels printed with the test token have "TEST" stamped on them and aren't accepted by USPS. Use them to verify the flow works end-to-end (buyer pays → label PDF generates → tracking number stored). Switch to the live token only when you're ready to ship real packages.
3 · Configure ship-from address
For ToT (single global address)
ToT uses one ship-from for the whole marketplace — set in Railway env vars:
SHIP_FROM_NAME=Your Shop LLC
SHIP_FROM_STREET=1301 Justin Rd Ste 201 PMB 1099
SHIP_FROM_CITY=Lewisville
SHIP_FROM_STATE=TX
SHIP_FROM_ZIP=75077
SHIP_FROM_PHONE=4691234567
ADMIN_EMAIL=ops@yourdomain.com
If any of the six SHIP_FROM_* vars are missing, the shipping endpoint throws
"Ship-from address not configured" with the missing list. Previously it
would silently ship from a blank address — that bug's fixed, but the lesson stands.
For Storefront (per-shop address)
Storefront stores ship-from on each Storefront row. The operator sets it via:
- Operator visits
/dashboard/[slug]/settings - Scrolls to "Shipping" section
- Fills in name, street1, street2, city, state, zip, phone, email
- Saves → values persist on the Storefront row
Each shop ships from its own address. The Storefront's
POST /api/shippo/buy-label/[orderId] endpoint reads the relevant row and
passes it to Shippo at label-purchase time.
4 · Test the label flow
End-to-end test — Storefront
- Set test Stripe + test Shippo tokens on Railway
- As a buyer (use a different browser / incognito), purchase a card with test card
4242 4242 4242 4242 - Check the Stripe dashboard — payment should succeed
- Check the storefront
/dashboard/[slug]/orders— Order should bepaid - Click "Print Label"
- You should see:
- USPS label PDF download (with TEST stamp)
- Tracking number stored on the Order
- Buyer-shipped email sent (if Resend is configured)
Manual cURL test (no purchase needed)
curl -X POST https://api.goshippo.com/shipments \
-H "Authorization: ShippoToken shippo_test_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"address_from": {
"name": "Sender",
"street1": "215 Clayton St",
"city": "San Francisco",
"state": "CA",
"zip": "94117",
"country": "US",
"phone": "4151234567",
"email": "test@example.com"
},
"address_to": {
"name": "Recipient",
"street1": "1600 Pennsylvania Ave NW",
"city": "Washington",
"state": "DC",
"zip": "20500",
"country": "US"
},
"parcels": [{
"length": "6",
"width": "4",
"height": "1.5",
"distance_unit": "in",
"weight": "4",
"mass_unit": "oz"
}],
"async": false
}'
You should get back a JSON with a rates array containing USPS options. If you get
401 Unauthorized, your token is wrong. If you get a Shippo error about the address,
the FROM address is invalid (Shippo validates).
5 · Going live
- Shippo dashboard → Settings → Billing → add a payment method (Shippo charges per label)
- Settings → API → copy the live token (
shippo_live_...) - Replace
SHIPPO_API_TOKENon both ToT + Storefront services with the live token - Trigger redeploys
- Print one real label end-to-end with a real $1 purchase to confirm
Carrier rates + parcel sizing
Both repos hardcode a default parcel size (6×4×1.5 in, 4 oz) optimized for one slabbed card + bubble mailer. The shipping endpoint requests USPS rates only and picks the cheapest Ground/Priority option.
For multi-card lots, the parcel sizing assumption breaks down — you'll want to bump the
weight + dimensions in src/app/api/shipping/route.ts based on lot size. (Future:
per-listing parcel template.)
Address validation
ToT supports an explicit address-validation step (action: 'validate' on the shipping
endpoint) — Shippo checks the address against USPS's database and returns a confidence score.
Storefront doesn't currently use this — buyer addresses come from Stripe Checkout's
shipping_address_collection which Stripe pre-validates before submitting.
Buyer notifications
After a label is purchased, both repos fire a "buyer shipped" email with the tracking link.
ToT: src/lib/notify.ts → notifyBuyerShipped. Storefront: same shape via
Resend if RESEND_API_KEY is configured. Without Resend, emails log to console.
Production readiness checklist
- ✓
SHIPPO_API_TOKENset on both ToT + Storefront (live token) - ✓ ToT: all 7
SHIP_FROM_*+ADMIN_EMAILset - ✓ Storefront: at least one shop has its
shipFrom*fields populated via dashboard settings - ✓ Shippo billing payment method added
- ✓ Real label printed end-to-end on test inventory
- ✓ Buyer-shipped email arrives at buyer's inbox (or check Resend logs if not)