USPS
Priority Mail Intl
10–15 days$7.40
$1.90 of duties and taxes already inside the total
all-in · the amount the purchase will debit
A webhook turns shipping events into code you don't poll for: verify the signature, deduplicate by event id, then act. The highest-value recipe is the adjustment event — a post-shipment charge lands as a payload carrying the wallet delta, so your books update when the carrier rebills, not at month end.
Whether the label was USPS, UPS, FedEx, or DHL, the events your consumer receives are the same shapes — one integration covers all four carriers.
A post-shipment carrier rebill lands as its own record with the signed change to your prepaid balance — the payload your bookkeeping wants.
A webhook consumer is only as good as its model of what actually happens upstream. This is that model: an order arrives however it arrived, the label is drafted and the destination validated, and the purchase debits a prepaid wallet. Your endpoint sees the transitions — and if a carrier re-rates the parcel weeks later, it sees shipment.adjustment.created carrying the wallet delta.
reship on #5827 — new address, same parcel, please redo the label
However the order arrived — a synced storefront, a REST call, or a pasted message — it becomes the same shipment record. One consumer covers every rail instead of one consumer per channel.
Everything an event announces is also readable over the REST API, which is exactly what makes the reconciliation poll a free backstop rather than a second integration.
Shopify, Etsy, WooCommerce, TikTok Shop and the rest sync orders into the same To Ship queue the REST API writes to, and tracking pushes back on print. For an integration that is the whole point: your endpoint never learns a new payload shape per storefront, because the shipment is the same record whatever created it.
Via AItiles: an order pasted out of a DM becomes the same shipment record, and fires the same events, as a synced one.
Every recipe on this page sits on the same three-step spine, and the order is not negotiable. Verify the signature first, on the raw request body, before any parsing happens — an unsigned or badly signed request is not a shipping event, it is arbitrary input from the internet addressed to an endpoint that can move state in your systems. GoatLabels webhooks are signed for exactly this reason. Compare the computed signature to the received one in constant time, and treat verification failure as a hard reject, not a log line. The precise header names and algorithm belong to the API contract, so read them from the API docs rather than hard-coding what a tutorial — including this one — remembers them to be.
Second habit: deduplicate by event id. Deliveries can arrive more than once — a timeout on your side followed by a retry is the everyday case — so the consumer records each event id it has processed and drops repeats on arrival. One table, one unique index, one existence check. Third habit: make the action itself idempotent anyway. Set the order's state to fulfilled rather than toggling it; upsert the accounting entry keyed on the event id rather than appending blindly. Belt and braces, because the one duplicate that matters is the one that arrives the week your dedupe table was being migrated.
This is the highest-value consumer most sellers never build. A carrier that reweighs or remeasures a parcel after acceptance bills the difference later — what those rebills are, how each carrier detects them, and how to dispute one is the carrier billing adjustments page's territory. What matters here is the shape of the moment: money left your business days or weeks after the sale it belongs to, and in most bookkeeping setups nobody finds out until month end, when the wallet history and the ledger disagree by a number no one can explain.
The recipe closes that gap to minutes. GoatLabels posts each adjustment as its own record, and the shipment.adjustment.created event delivers it to your endpoint carrying the wallet delta — the signed amount by which your prepaid balance actually changed. The consumer verifies, dedupes, then writes one accounting entry: the delta against your postage cost account, keyed to the original order so the order's true margin updates too. Book it on the date the event arrived, because that is when the money moved. One edge case belongs in the handler from day one: when an adjustment exceeds the wallet balance, the remainder is charged to the card on file, so let the entry reflect where the money actually came from rather than assuming the wallet absorbed all of it.
The second recipe kills the copy-paste step between buying a label and telling everything else about it. When a label purchase event arrives, the consumer looks up the order by your own reference, writes the tracking number and carrier onto it, and moves it to fulfilled — the same three edits a person makes by hand, minus the person. Because the write is an upsert keyed on the shipment, a retried delivery re-applies the same state instead of appending a second tracking number, which is the difference between an idempotent consumer and a support ticket.
What you should not rebuild is the buyer-facing half. GoatLabels already sends branded tracking emails and hosts a branded tracking page per shipment, so the consumer's job is internal state — your order system, your warehouse queue, your support tool's timeline — not re-notifying the buyer. Where this pattern earns its keep is volume: a store syncing hundreds of orders a day cannot afford a human between the label printer and the order system, and an event-driven consumer is how the two stay agreed without one.
The uncomfortable truth about any webhook integration — every provider, not just this one — is that your endpoint will eventually be down while something important happens. Deploys, expired certificates, a cloud region having a bad afternoon. Two design rules make that survivable. First, acknowledge fast: accept the verified event, queue it durably, and return success in milliseconds, doing the slow work — database writes, third-party calls — off the request path, so your own latency never turns a healthy delivery into a retry storm.
Second, reconcile: run a periodic job that lists recent shipments and adjustments over the REST API and repairs anything your event log missed. Calls are unlimited on every plan, so the backstop poll costs nothing but the cron entry, and the delivery and retry contract itself is documented in the API docs — build against that, not against assumptions. Webhooks for freshness, reconciliation for certainty is the architecture that survives an outage without a human replaying anything by hand. It is also, not coincidentally, the pattern that makes switching costs low: consumers built this way are what API developers comparing platforms and teams pricing per-call APIs end up porting in an afternoon.
Every recipe above assumes the same substrate: one balance, one shipment record per label, and a correction that arrives as its own row rather than as a line in a monthly PDF. These are those pieces, from the consumer's side of the wire.
Card, Apple Pay, Google Pay, ACH, and crypto all top up the same wallet, so an adjustment delta is a signed change to one number rather than a movement between funding paths your code would have to model. Note the one edge case for the handler: an adjustment larger than the balance charges the remainder to the card on file, so book where the money actually came from.
Reships, make-goods, the sale that arrived as a DM. Billy drafts those from pasted text, a screenshot, or a spoken address, and they land as ordinary shipment records — so the manual path and the automated path produce one event stream, not two.
A rate comes back as one all-in number and that exact amount leaves the balance at purchase. When a carrier re-rates the parcel afterwards, the difference posts as its own record and its own event, which is what makes the accounting recipe possible at all — there is a specific thing to book, on a specific date.
USPS, UPS, FedEx, and DHL are quoted and bought through the same endpoints, so your consumer does not branch per carrier. Whichever one won the parcel, the purchase, the void, and the adjustment arrive in the shapes you already handle.
Verify on the raw body, dedupe by event id, act idempotently. Then back it with a reconciliation poll: every record an event announces is readable over REST, and calls are unlimited on every plan, so certainty costs a cron entry rather than a bill.


Shipment purchased
Event queued · record readable over REST
An outage is a gap to backfill, not information that is gone — which is easier to believe when you can read the real state from a phone at the weekend. Everything a delivery would have told you is a record you can open, in order, without a terminal.
Before you dig through a delivery log, ask. Billy pulls the shipment's own history — bought, voided, adjusted, delivered — and reads it back, which is usually the fastest way to tell a delivery your endpoint missed from an event that was never going to fire. Included on every plan. This is the real product, not a render.

Listening
"What happened to the shipment for order 5827?"
Out for delivery
Austin, TX · scanned this morning
Speaks plain English · reads the same records the REST API returns · phone, browser, or Telegram on Pro.
A rate object here is not a base price waiting for surcharges to be attached further down the pipeline. Each carrier comes back as a single all-in total with duties and taxes already inside it, which is why the figure your code shows a buyer is the figure the purchase later debits — and why a later difference is an adjustment event rather than an argument. Figures are illustrative.
Priority Mail Intl
10–15 days$7.40
$1.90 of duties and taxes already inside the total
all-in · the amount the purchase will debit
Intl Connect Plus
3–5 days$8.90
$2.20 of duties and taxes already inside the total
all-in · the amount the purchase will debit
Express Worldwide
1–2 days$9.80
$2.50 of duties and taxes already inside the total
all-in · the amount the purchase will debit
One Bearer header, JSON in both directions, signed webhooks instead of polling loops, and real idempotency keys so a retried job never buys a label twice. Everything an event announces is also readable over REST — unlimited calls on every plan — which is what makes the reconciliation backstop free to run.
POST /api/v1/shipments
Authorization: Bearer sk_live_…
Idempotency-Key: ord_8421
{
"to": { "name": "Joyce", "city": "Berlin", "country": "DE" },
"parcel": { "weight": 2.6, "weight_unit": "lb" },
"service": "fedex_intl_priority"
}
The reconciliation backstop in the recipes above only makes sense if polling is free, so it is: REST calls are unlimited on every plan including Free, and signed webhooks are not a tier you buy into. Free covers 50 shipments a month; Pro is a flat plan for volume, and it does not change one line of your consumer.
Volume pricing
illustrativemore volume → lower per-label rates
$0 / forever
Build the consumer, leave the backstop cron running, and pay for labels only.
Included on every plan, and orthogonal to your integration: a label drafted by Billy and a label POSTed by your code become the same shipment record and emit the same events.
GET /api/v1/shipments
200 OK · plan: free
Unlimited calls on Free — still unlimited on Pro.
Bring your invoice — on Pro we work with you to beat the rates you're getting from
ShippoPirateShipShipStationLabel prices are quoted live from USPS, UPS, FedEx, and DHL as one all-in number, and that exact amount debits the prepaid wallet on purchase — which is the figure a later adjustment delta corrects against. Full pricing details
The habits are identical every time — verify, deduplicate, act. What differs is which record each team cannot afford to miss.
Finance-adjacent engineering
The highest-value consumer most sellers never build. shipment.adjustment.created carries the signed change to the balance, so postage cost and true order margin update days after the sale instead of at month end, when nobody can explain the difference.

Purchase event to fulfilled state
Look the order up by your own reference, write the tracking number and carrier onto it, mark it fulfilled. The same three edits a person makes by hand, minus the person — and minus the hour a day at volume.
Upsert, never append
A retried delivery re-applies the same tracking number
Tracking state on the ticket timeline
Consumers that move in an afternoon
0
per-call cost, so the reconciliation poll is free to leave running
A video took off and I had 80 TikTok Shop orders by morning. I printed every label from my phone on the bus to the post office.
Early customer · TikTok
TikTok Shop seller
I run my Etsy shop from the kitchen. The AI reads the order, picks the carrier, the label prints. That's the whole workflow now.
Early customer · Etsy
Etsy maker
The API is what every shipping API pretends to be. Idempotency that actually works.
Sasha R.
Staff engineer, marketplace
Verify, deduplicate, act: point an endpoint at your shipping events, wire the adjustment delta into your books, and let the reconciliation poll sleep unless something breaks.
Sign up in seconds. No card required. Unlimited calls and signed webhooks on every plan.