# Shippo Shipping Labels for GoCommerce — Open-Source Module

> Buy Shippo labels from a GoCommerce order in one call: API token, carrier account, service level, ship-from address, and what the module leaves out.

- Canonical: https://kitcommerce.store/integrations/fulfill-shippo/
- Last updated: 2026-09-25

---

Shipping

## Shippo shipping labels for GoCommerce

A Go package that buys a label through Shippo when an operator ships an order, and hands the engine the tracking number and the label URL. One call, on a carrier account and service level you choose — no rate shopping, by design.

- **Shipping** module
- **12** settings
- **8** tests
- **ext/fulfill-shippo**

### What it does

The module registers “shippo” as a fulfillment provider. An operator ships through the engine’s own POST /api/admin/create-fulfillment with the provider shippo — the engine still owns the order’s state and its events, and the module only talks to Shippo.

It buys the label with Shippo’s instant transaction: the shipment, the carrier account and the service level go in one synchronous POST /transactions, and the answer is a tracking number and a label URL, or an error. The package explains why it skips Shippo’s usual shipment, rates, buy sequence: rate shopping is a decision about price and delivery date that belongs to whoever is packing the parcel, and picking the cheapest automatically would quietly put somebody’s express order on a slower service.

In the admin it describes itself as “Multi-carrier labels through Shippo: an instant purchase on the carrier account and service you name.” It speaks Shippo’s REST API over Go’s standard library, pinned to API version 2018-02-08, with no SDK.

### Configuration

Five things are required before it counts as set up: the API token, the carrier account, a default service level, and a ship-from street and country. Set them in Config from your own main(), or leave Config empty and fill them in under Settings › Shipping providers.

*Shippo module settings — 12 settings, 5 required*

| Setting | Environment variable | Required | What it does |
| --- | --- | --- | --- |
| `APIKey` API token | `SHIPPO_API_KEY` | Yes | A Shippo API token, live or test, sent as ShippoToken in the Authorization header. |
| `CarrierAccount` Carrier account | `SHIPPO_CARRIER_ACCOUNT` | Yes | The object id of the carrier account the label is bought from. An instant purchase has to name the account being charged; meta carrier_account overrides it for one parcel. |
| `ServicelevelToken` Service level | — | Yes | The default service — usps_priority, ups_ground and the rest. Meta servicelevel_token overrides it for one parcel. |
| `From.Street1` From: street | — | Yes | Where parcels are sent from. The order knows where it is going, never where it came from, so this is configuration. |
| `From.Country` From: country (ISO 2) | — | Yes | The ship-from country, as two letters. |
| `From.Name, From.City, From.State, From.Zip, From.Phone` From: name, city, state, ZIP, phone | — | No | The rest of the ship-from address. |
| `From.Company, From.Street2, From.Email` | — | No | Also part of the ship-from address, settable in Go only; not in the panel. |
| `LabelFileType` Label format | — | No | PDF_4x6, PNG, ZPLII and the rest. Empty leaves the Shippo account’s own default. |
| `DefaultWeightGrams` Default weight (grams) | — | No | Per unit, for a parcel holding a variant with no weight recorded. 500 by default; a fully weighed catalogue never reaches it. |
| `DefaultLengthMM, DefaultWidthMM, DefaultHeightMM` | — | No | The box sent when the engine has no unambiguous size, which is any parcel holding more than one unit. 150 × 150 × 100 mm by default. Go only. |
| `BaseURL` API base URL | — | No | Overrides https://api.goshippo.com, for tests. Empty for production. |
| `Client` | — | No | Replaces the HTTP client, which otherwise times out after 30 seconds. Go only; not in the panel. |

The module reads its Config struct, not the environment. The variable names are the ones the package’s own example or the reference binary uses; in your own `main()` you choose where each value comes from. Where a setting has a panel label, the admin’s settings drawer can hold it too, and a value typed there wins over Config.

### Setting it up

1. **Pick an account and a service** — In Shippo, note the object id of the carrier account to buy from and the service level token to use by default, and create an API token — live or test.
2. **Install the module** — Import it and pass shippo.New to gocommerce.New, as below — or run the reference binary with -carriers, which installs every carrier module idle.
3. **Fill in the settings** — Token, carrier account, service level and ship-from address, in Config or under Settings › Shipping providers. A value typed into the panel counts on the next shipment, with no restart.
4. **Ship an order** — POST /api/admin/create-fulfillment with the order id and the provider shippo. The engine records the tracking number and label URL and moves the order to shipped, or to partial.

main.go

```
import (
	"os"

	"github.com/itswadesh/gocommerce/core"
	shippo "github.com/itswadesh/gocommerce/ext/fulfill-shippo"
)

app, err := gocommerce.New(cfg,
	shippo.New(shippo.Config{
		APIKey:            os.Getenv("SHIPPO_API_KEY"),
		CarrierAccount:    os.Getenv("SHIPPO_CARRIER_ACCOUNT"),
		ServicelevelToken: "usps_priority",
		From: shippo.Address{
			Name: "Acme", Street1: "215 Clayton St", City: "San Francisco",
			State: "CA", Zip: "94117", Country: "US", Phone: "+15553334444",
		},
	}),
)
```

The package doc’s own example, with its imports; cfg is your gocommerce.Config. With an empty shippo.Config the module installs idle and waits for the panel. Import path `github.com/itswadesh/gocommerce/ext/fulfill-shippo`.

### How it works

- **One synchronous purchase**

  The label is bought with async set to false, so Shippo answers with the result rather than QUEUED. An asynchronous purchase would leave the engine holding a shipment with no tracking number — the half-booked state an operator cannot act on.

- **A 200 is not a label**

  Shippo can answer 200 or 201 with status ERROR and the reason in its messages. The module checks the status and the tracking number, so a refused purchase is an error the operator sees, never an order moved to shipped.

- **The parcel the engine measured**

  Weight comes from the variants when every line has one, otherwise from the default per unit. Sizes come from the engine only for a single unit of a single variant, else the configured box. Grams and millimetres go to Shippo unconverted, so nothing is rounded.

- **Per-parcel overrides**

  Meta on the ship request can name a different servicelevel_token or carrier_account for one parcel — which is where an operator who compared rates elsewhere passes the one they chose.

- **Carrier read from the service**

  The carrier code comes from the service level’s prefix — USPS, UPS, FedEx, DHL, Aramex, Canada Post, Australia Post — so the tracking number gets the right link. Anything else is left for the engine to work out from the number.

- **Settings without a restart**

  Before each shipment the panel’s values are laid over Config. With nothing in Config the module installs idle; with the required fields in Config it starts switched on.

### What it does not do

Read these before an order depends on it. No store is known to run GoCommerce in production yet, so these come from the code, not from anyone’s experience.

- **No rate shopping** — One carrier account and one service level, from settings or meta. The module never asks Shippo for rates or picks the cheapest.
- **No tracking updates** — The module keeps no state and registers no webhook. Scans and delivery events in Shippo do not reach the order; marking it delivered is still the operator’s job.
- **No void call** — There is no call to cancel a label. Deleting the fulfillment in GoCommerce does not touch the label or its postage in Shippo.
- **One box per label** — Each fulfillment buys one label for one parcel. A shipment in three boxes is three fulfillments.
- **No customs declaration** — The request carries no customs items, so a cross-border parcel that needs a declaration is not covered by the module as written.
- **A guessed box for several items** — For anything but a single unit, the size sent is the configured default, not a measurement. The engine does not pack cartons, and says so.

FAQ

### Questions about the Shippo module

**Can an operator choose the service per order?**

Yes. The ship request’s meta takes servicelevel_token and carrier_account, and either overrides the configured default for that one parcel. Comparing rates happens elsewhere; the module buys what it is told.

**Where does the label come from?**

Shippo hosts it. The label URL in Shippo’s answer is stored on the shipment beside the tracking number, in the format the Label format setting names or, when that is empty, your Shippo account’s default.

**Can I use it without writing Go?**

Yes. The reference binary’s -carriers flag installs every carrier module with an empty Config. Switch Shippo on under Settings › Shipping providers and fill in the token, carrier account, service level and ship-from address there.

**Is this a Shippo partnership?**

No. The module calls Shippo’s public API with a token you supply. Shippo does not endorse GoCommerce, and no store is known to run this module in production yet.

### Source

Everything on this page is read from [`ext/fulfill-shippo`](https://github.com/itswadesh/gocommerce/tree/main/ext/fulfill-shippo) in the GoCommerce repository, MIT licensed. When this page and the code disagree, the code is right and this page is out of date.Shippo is a trademark of its owner; this module talks to its public API and implies no endorsement. See [trademarks](https://kitcommerce.store/about/#trademarks).

- [ext/fulfill-shippo on GitHub](https://github.com/itswadesh/gocommerce/tree/main/ext/fulfill-shippo)
- [Shippo](https://goshippo.com)
- [All GoCommerce modules](https://kitcommerce.store/integrations/)

### Try it against a store of your own

The one-command stack gives you GoCommerce’s API and admin on your own machine in minutes. Add this module to it and try it on test orders before a real one depends on it.

[Deploy in minutes](https://kitcommerce.store/#one-command) · [Read the module](https://github.com/itswadesh/gocommerce/tree/main/ext/fulfill-shippo) · [All integrations](https://kitcommerce.store/integrations/)
