Buy the label
Your existing carrier integration returns the label. Ask for ZPL when it is going to a thermal printer.
carrier.buyLabel(shipment)Shipping & fulfillment
Your system already knows the moment a label is bought. The only missing step is getting it onto the thermal printer at the right pack bench — without a browser, a print dialog, or a person.
The gap
Carrier APIs are excellent. The last six feet — from an API response to a specific printer in a specific building — is where fulfillment teams end up with browser extensions, shared folders, and one person who knows the trick.
Someone has to have the tab open, pick the right printer, and click. At volume that is a full-time job nobody applied for.
And it should not be. Your code cannot reach a printer inside a warehouse, and no IT team is going to make that possible.
A redrawn 4×6 label scans worse than the ZPL your carrier already gave you. Carriers return ZPL for a reason.
Without a job record, "did that label come out?" is answered by walking to the printer.
The flow
One request added to the code you already have. No queue to run, no polling loop, and no local software to write.
Your existing carrier integration returns the label. Ask for ZPL when it is going to a thermal printer.
carrier.buyLabel(shipment)Base64-encode the ZPL, name the printer at that bench, and add an idempotency key built from your shipment ID.
POST /v1/print-jobsStore it against the shipment. When someone asks whether a label printed, the answer is a lookup rather than a walk to the warehouse.
shipment.printJobId = job.idThe integration
This is the whole thing. There is nothing to run in the background and no local software to ship.
const RocketPrint = require("rocketprint");
const rocketprint = new RocketPrint(process.env.ROCKETPRINT_KEY);
export async function onLabelPurchased(shipment) {
// The carrier already gave us ZPL — send it through untouched.
const job = await rocketprint.printJobs.create({
printerId: shipment.packBench.printerId, // stored per bench
title: `Shipment ${shipment.id}`,
contentType: "raw_base64",
content: Buffer.from(shipment.labelZpl).toString("base64"),
source: "fulfillment-worker",
}, {
// Derived from your own ID: a retry can never print a second label.
idempotencyKey: `shipment-${shipment.id}-label`,
});
await shipments.update(shipment.id, { printJobId: job.id });
}Store a printer ID against each bench or location in your own data. Choosing where something prints becomes an ordinary decision in your code.
Build the idempotency key from your shipment ID. Your code can retry as often as it likes; the second request returns the first job.
What changes operationally
Labels appear at the bench as orders come through. The packer picks the label up instead of operating a browser.
Jobs wait while a bench computer restarts, then print in order when it comes back. The shift does not stop for a Windows update.
Every label has a job ID, a status, and timestamps. "Did label 1042 print?" is a query with a real answer, including the failure reason if it did not.
Carrier ZPL reaches the printer untouched, so the printer draws the barcode at its own full resolution instead of it being redrawn on the way. See the formats →
Questions
Yes, but ask your carrier for ZPL if it is going to a thermal printer. Most carriers offer it. A PDF prints through that computer's driver, which decides scaling — fine for a packing slip, less predictable for a 4×6 label.
Each printer has an ID that never changes. Store that ID against your bench, pick list, or location, and pass it with the job. Choosing where something prints is one field in your request — not a networking job.
The job waits and prints when the computer wakes up. Your order webhook does not fail, and you do not need a retry queue of your own.
Send an Idempotency-Key built from your own order or shipment ID. A repeated request returns the original job instead of printing a second label — which in fulfillment is a wrong parcel waiting to happen.
Send two jobs — the label as raw ZPL to the thermal printer, the packing slip as a PDF to the laser printer. Same API, two printer IDs.
Start with one bench
If it works for one bench it works for fifty — the only thing that changes is which printer ID you send.