GoatLabels GoatLabels
Signed events, not polling loops.

Webhook recipes: verify, deduplicate, act


Three habits, then the recipes write themselves.

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.

Events from every carrier lifecycle

Whether the label was USPS, UPS, FedEx, or DHL, the events your consumer receives are the same shapes — one integration covers all four carriers.

UPSFedExDHLUSPS all four
Verify first Reject before parsing.
Dedupe by id One indexed column.
Then act Idempotent side effects.

The adjustment event carries the wallet delta

A post-shipment carrier rebill lands as its own record with the signed change to your prepaid balance — the payload your bookkeeping wants.

VisaMastercardAmerican ExpressApple PayGoogle PayBitcoinEthereumTether (USDT)
Own ledger entry Never a mystery charge.
Signed delta Books update on arrival.
Replay-safe Duplicates post once.
The lifecycle your endpoint is subscribing to

Every step below fires an event you can consume.

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.

1
Order in, over any rail
Store sync, REST call, or paste
ops channel

reship on #5827 — new address, same parcel, please redo the label

Shopify
Synced order — same event stream
TikTok Shop
Synced order — same event stream
What a consumer stores per event
Event id
The dedupe key, one indexed column
Shipment
Matched to your own order reference
Signature
Verified on the raw body before parsing
Received at
The date the accounting entry is booked on
Or POST it yourself — the API writes the same record

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.

2 Billy AI

Validated before purchase, so fewer events are corrections.

Billy AI
  • Drafts the label from a pasted order, a screenshot, or a spoken address
  • Validates the destination before anything is bought or announced
  • Trims the void-and-rebuy traffic your consumer would otherwise have to handle
  • Orthogonal to the API — a Billy draft and a POST write the same record and emit the same events
Verified. Deduplicated. Then acted on.
3
Label bought, event emitted
Signed on delivery, readable over REST
Debited from the prepaid wallet
Card, Apple Pay, Google Pay, ACH, or crypto
all-in
USPS
Purchase event delivered
Tracking number on the shipment record
all-in

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.

Thirteen order rails, one event stream

One consumer covers every channel they sell on.

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.

Shopify logo Shopify
Etsy logo Etsy
WooCommerce logo WooCommerce
TikTok Shop logo TikTok Shop
Instagram logo Instagram AI Facebook logo Facebook AI
Amazon logo Amazon
eBay logo eBay
Show 15 more Show less
Squarespace logo Squarespace
Wix logo Wix
Walmart logo Walmart
Shopify Plus logo Shopify Plus Soon
Square logo Square
BigCommerce logo BigCommerce
Magento logo Magento
Zapier logo Zapier Soon
NetSuite logo NetSuite Soon
Ecwid logo Ecwid Soon
Temu logo Temu
Shein logo Shein Soon
ChannelAdvisor logo ChannelAdvisor Soon
SkuVault logo SkuVault Soon
Zoho Inventory logo Zoho Inventory Soon

Webhook hygiene: signature verification and idempotent consumers

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.

Recipe: adjustment event to accounting entry, end to end

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.

Recipe: label purchase to order-system fulfillment update

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.

Retries, outages, and replay: building a consumer that survives them

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.

The five things a consumer is consuming

Events are only useful
if the records behind them are.

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.

One prepaid balance
the deltas resolve against

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.

Wallet balance
$1,247.36 USD
AI

The orders nobody
is going to script

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.

Billy LIVE
Where's the package to Maya?
It's in transit via UPS. Expected Friday, Aug 7.
UPS
1Z8923A6B05G4123456
In transit
Austin, TX → Dallas, TX
Ask Billy anything...

The quote is the debit,
the adjustment is its own row

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.

Shipment summary
UPS Ground $12.49
Insurance $0.75
Fuel surcharge $0.32
Total $13.56

Four carriers,
one set of event shapes

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.

Compare rates
UPS UPS $12.48
FedEx FedEx $13.22
DHL DHL $14.10
USPS USPS $11.79
Duties & taxes paid

Signed webhooks,
and a free way to check them

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.

POST /v1/shipments
{
"to": { "name": "Maya" },
"from": { "name": "Alex" },
"service": "ups_ground",
"label_format": "pdf"
}
Webhook delivered
GoatLabels mobile shipments queue showing each shipment's current state
Billy drafting a label from a pasted order on mobile

Shipment purchased

Event queued · record readable over REST

The dashboard your consumer is mirroring

When the endpoint is down, the records are still there.

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.

Read a shipment's actual state before you decide to replay anything.
Purchases, voids, and adjustments each read back as their own entry, in sequence.
Buy or void by hand while the automation is paused — same records, same events on recovery.
One-tap print to your share sheet or a thermal printer over Wi-Fi.
Paste an order and Billy drafts it, for the reship your pipeline cannot originate itself.
Billy reads the record, not your logs

Ask what actually happened to a shipment.

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.

goatlabels.io/dashboard/shipments — Billy
Billy reading a shipment's tracking history back over the purchased labels list

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.

What the rates call returns

One all-in number per carrier, per parcel.

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.

US → DE 2.6 lbs 12×8×4 in
USPS

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

Flagged in the response
FedEx

FedEx

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

DHL

DHL

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

No day-after invoice to reconcile by hand A granted void credits the wallet and emits its own event Amount and currency on every record
Signed webhooks · REST API v1

The events are the product, the recipes are yours.

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.

Signed webhooks — verify on the raw body before you parse
shipment.adjustment.created carries the wallet delta for your books
Unlimited REST calls make a reconciliation poll a free backstop
OpenAPI 3.1 spec, no SDK lock-in — the contract lives in the docs
Read the webhook docs Get a free API key $0 webhooks and API, every plan
curl https://api.goatlabels.io/api/v1/shipments
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"
}
Pricing that does not meter your integration

Unlimited calls. Signed webhooks. Every plan.

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.

Pro

Most popular
$40 / month
Unlimited shipments — no monthly cap on what your code buys
Cheaper per-label pricing as volume grows, with no change to the contract
Billy AI on Telegram — for the reship nobody wants to script
API access still free and unlimited

Volume pricing

illustrative
less volume more volume

more volume → lower per-label rates

Free

$0 / forever

Build the consumer, leave the backstop cron running, and pay for labels only.

Unlimited REST calls — a reconciliation poll costs a cron entry, not a bill
Signed webhooks included, never a paid add-on
50 shipments a month, no monthly minimum to print
USPS, UPS, FedEx, and DHL through the same rates call and the same event shapes
Billy AI in the web app, unlimited — for the orders no integration will ever cover

Billy is not an upsell

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.

Web — every plan Telegram — Pro

API free on every plan

GET /api/v1/shipments

200 OK · plan: free

Unlimited calls on Free — still unlimited on Pro.

We'll beat your current rates

Bring your invoice — on Pro we work with you to beat the rates you're getting from

ShippoPirateShipShipStation
No per-call pricing No webhook tier Start in seconds
USPS UPS FedEx DHL

Label 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

Who actually builds these consumers

Four teams, four reasons to consume events.

The habits are identical every time — verify, deduplicate, act. What differs is which record each team cannot afford to miss.

Teams wiring adjustments into the books

Finance-adjacent engineering

Wallet delta

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.

1 entry per adjustment keyed to the order booked on arrival
goatlabels.io/dashboard/shipments
GoatLabels shipments queue — the records a webhook consumer mirrors

Order systems that hate copy-paste

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

Support tools answering "where is it"

Tracking state on the ticket timeline

buyer's asking where order 5827 got to
Tracking state on the timeline · no tab switch

Teams porting off a metered API

Consumers that move in an afternoon

0

per-call cost, so the reconciliation poll is free to leave running

Shipping webhooks, answered

How do I verify a webhook signature?
Compute the expected signature over the raw request body using the endpoint's shared secret, compare it to the signature header with a constant-time comparison, and reject anything that fails before you parse a single byte of JSON. The exact header name and algorithm are part of the API contract, so take them from the API docs rather than from a blog post — including this one.
What happens if my endpoint is down when an event fires?
Design as if any individual delivery can be late or lost, whatever the delivery guarantees are: the delivery and retry contract is documented in the API docs, and the durable backstop is reconciliation — a periodic job that lists recent shipments and adjustments over the REST API and fills any gap in your local state. Unlimited API calls mean the backstop costs you engineering time only, not a metered bill.
How do I handle duplicate webhook deliveries?
Record each event's unique id when you process it, and drop any delivery whose id you have already seen — one indexed column and one existence check. Pair that with idempotent side effects, so that even a duplicate that slips past the check re-applies the same state instead of double-posting an accounting entry or double-notifying a buyer.
What does the adjustment webhook payload contain?
The shipment.adjustment.created event identifies the adjusted shipment and carries the adjustment record itself, including the wallet delta — the signed amount by which your prepaid balance changed. The current field-by-field schema lives in the API docs. What an adjustment is and how to dispute one is a separate question, answered on the carrier billing adjustments page.
Can I automate without webhooks by polling the API?
Yes. Every record a webhook announces is also readable over the REST API, and calls are unlimited on every plan, so a cron job that lists recent shipments and adjustments is a legitimate architecture — simpler to operate, at the cost of latency. Most consumers land on both: webhooks for freshness, a reconciliation poll for certainty.
Real sellers, real words

Sellers, and the engineers who wire them up.

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.
E

Early customer · TikTok

TikTok Shop seller

TikTok Shop
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.
E

Early customer · Etsy

Etsy maker

Etsy
The API is what every shipping API pretends to be. Idempotency that actually works.
S

Sasha R.

Staff engineer, marketplace

API user
Unlimited API calls. Signed events. Every plan.

Stop polling for shipping state.
Start consuming it.

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.