Lifted ShipKit: open-source multi-carrier shipping labels with forced 3-D Secure
Shipping labels are one of those features that look trivial until you actually build them. You need a carrier-rating API, a payment step, PCI scope for the card data, and — the part nobody warns you about — a fraud probl
Shipping labels are one of those features that look trivial until you actually build them. You need a carrier-rating API, a payment step, PCI scope for the card data, and — the part nobody warns you about — a fraud problem, because labels bought with stolen cards are a top chargeback category. Real goods, shipped fast, disputed weeks later.
We open-sourced Lifted ShipKit to collapse that whole stack into a few lines of code. It's MIT-licensed, self-hostable, and it ships with a drop-in checkout widget that forces 3-D Secure on every card charge.
Repo: https://github.com/LiftedHoldings/lifted-shipkit
What it actually is
ShipKit is a Kotlin/JVM backend plus a dependency-free browser widget. The backend wraps EasyPost for real multi-carrier shipping — address verification, live USPS/UPS/FedEx rate compare, SmartRates, label purchase, batch, scan forms, customs, and tracking webhooks — and adds a payment layer on top of it. The widget is a UMD global (window.ShipKit), so it mounts in plain HTML, React, Vue, Svelte, or anything else with a <script> tag and no build step.
The part I want to talk about is the payment layer, because that's the design decision that makes ShipKit different from "just call EasyPost yourself."
Why forced 3-D Secure is the whole point
Most teams that add shipping wire a rating API straight to a payment processor and then quietly own two things they didn't sign up for: the PCI scope for card data, and the chargeback liability when a fraudster buys a label with a stolen card.
ShipKit forces 3-D Secure on every card charge. The issuer authenticates the buyer before the label prints, which shifts fraud-and-chargeback liability off the merchant. Card data goes through hosted fields, so it stays out of your PCI scope. This isn't an enterprise upsell or a config flag you can forget to turn on — it's always on, by construction.
For a product whose fraud profile is "real goods, shipped immediately, disputed later," making authentication non-optional is the correct default.
60-second try
No account, no keys — the published Docker image comes up immediately (shipping and payment features return 503 until you supply an EasyPost key and 3DS credentials):
docker run --rm -e SHIPKIT_PORT=8080 -p 8080:8080 ghcr.io/liftedholdings/lifted-shipkit
Then open http://localhost:8080.
Drop it into your own checkout
The headline use case is keeping your app and your checkout and adding ShipKit for the rate-compare -> pay -> print-label step. Mount a node, point it at your backend, wire a callback:
<div id="ship"></div>
<script src="/js/shipkit.js"></script>
<script>
ShipKit.init({
mount: '#ship',
endpoint: '/api',
apiKey: 'pk_live_your_publishable_key', // publishable pk_… key — safe in the browser
onPurchase: ({ trackingCode, labelUrl }) => {
// hand the finished label back to your order flow
}
});
</script>
Keys come in two scopes: the browser widget uses a publishable pk_… key (the backend confines it to the customer flow), while your server calls use a secret sk_… key that never appears in client code. Every surface restyles with --sk-* CSS variables — no !important, no build step.
Self-host it fully
git clone https://github.com/LiftedHoldings/lifted-shipkit.git
cd shipkit
cp .env.example .env # EASYPOST_API_KEY + your 3DS keys
./gradlew build # Kotlin 2.0.21, JVM 17
./gradlew shipkitKeygen -Plabel=my-store # mint an API key
./gradlew run # API + widget on :8080
Every setting is read from the environment. Two optional, env-driven hooks let a platform operate a fleet of instances from a control plane — a bearer-guarded POST /api/config/markup to update the shipping markup remotely, and a fire-and-forget label.purchased webhook (carrier cost and buyer charge in integer cents) — both off by default when their tokens are unset.
Shipping as a profit center
One design choice worth calling out for anyone building a store: whoever owns the payments account owns the markup. ShipKit prices every label at the carrier rate plus your own percentage and fixed fee (percentage_markup + fixed_fee_cents), applied server-side and shown at checkout. Self-host it and that margin is 100% yours on every label.
Why open source it
Because the honest version of this tool is one you can read, fork, and run yourself. MIT license, dependency-free frontend, clean modular backend, no per-label SaaS fee when you self-host. If you'd rather not run infra, there's a managed tier and a 3-D Secure merchant-account tier — but the code, the widget, and self-hosting stay free.
- Live demo: https://liftedholdings.com/shippingtool
- Repo + docs: https://github.com/LiftedHoldings/lifted-shipkit
- The payments side (interchange-plus merchant services with 3-D Secure, built by the same team): https://liftedpayments.com
If you build something with it, or you want ShipKit woven into your own framework, we'd genuinely like to hear about it. PRs and issues welcome.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.