Agent skill
Shippo Webhooks Skill
Receive and verify Shippo webhooks. Use when setting up Shippo webhook handlers, debugging Shippo-Auth-Signature HMAC verification, securing endpoints with a URL token or IP allowlist, or handling shipping events like track_updated, transaction_created, transaction_updated, batch_created, batch_purchased, or automations_label_resolved.
Install this skill
npx skills add hookdeck/webhook-skills --skill shippo-webhooks
When to Use This Skill
- How do I receive Shippo webhooks (tracking updates, label purchases, batches)?
- How do I verify the Shippo
Shippo-Auth-Signatureheader? - How do I secure a Shippo webhook endpoint without HMAC (URL token, IP allowlist)?
- How do I handle
track_updatedortransaction_createdevents? - Why is my Shippo webhook signature verification failing?
How Shippo Secures Webhooks
Shippo offers three options, which can be combined:
- HMAC signature (strongest). Not self-serve: you ask your Shippo account manager (or sales) for "HMAC Webhook Setup", and Shippo provisions the secret. Setup can take up to 10 business days.
- Self-generated token in the webhook URL, e.g.
https://your.app/webhooks/shippo?token=<random>. Shippo sends it back as a query parameter, so you compare it to your stored value. - IP allowlist of Shippo's published US/EU egress IPs (see references/setup.md).
Verification (core)
The HMAC header looks like Shippo-Auth-Signature: t=1688493073,v1=24036c00…. The signature is the hex HMAC-SHA256 of "{t}.{raw body}", keyed with the secret Shippo gave you. No Shippo SDK has a verification helper, so you verify manually, against the raw request body (don't JSON.parse first).
const crypto = require('crypto');
// Replay check is off by default (toleranceSec = 0): retries and replays carry the
// original timestamp. Pass a positive toleranceSec to opt in.
function verifyShippoSignature(rawBody, header, secret, toleranceSec = 0) {
const parts = header.split(',').map((part) => {
const i = part.indexOf('=');
return i === -1 ? [part.trim(), ''] : [part.slice(0, i).trim(), part.slice(i + 1).trim()];
});
const t = parts.find(([k]) => k === 't')?.[1];
const sigs = parts.filter(([k]) => k === 'v1').map(([, v]) => v);
if (!t || !/^\d+$/.test(t) || sigs.length === 0) return false;
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(t));
if (toleranceSec > 0 && age > toleranceSec) return false;
const expected = crypto.createHmac('sha256', secret)
.update(`${t}.`).update(rawBody).digest('hex');
return sigs.some((sig) => sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));
}
Without HMAC, compare the token query parameter to your stored token with a timing-safe comparison. The examples support both modes.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Common Event Types
The event name is in the JSON body's event field; the object is in data.
| Event | Triggered When | data contains |
|---|---|---|
track_updated | A tracked shipment's status changes | Tracking object |
transaction_created | A transaction (label purchase) is created | Transaction object |
transaction_updated | A transaction is updated | Transaction object |
batch_created | A batch finishes creating (async) | Batch info |
batch_purchased | A batch's labels finish purchasing (async) | Batch info |
automations_ | A Shippo Automations label request reaches a terminal state | Automations label result |
Subscribing to all delivers every event type. Payloads include a test boolean, and requests carry a Shippo-API-Version header.
For the full event reference, see Shippo's webhook documentation.
Delivery Behavior
- Return a
2xxwithin 3 seconds. Process heavy work asynchronously. - Shippo retries twice on timeouts,
408,429, or5xx. Other4xxresponses are not retried. - There is no event ID. Dedupe on natural keys (e.g.
request_idforautomations_,label_ resolved object_id+statusfor transactions).
Environment Variables
SHIPPO_WEBHOOK_SECRET=your_hmac_secret # HMAC secret provisioned by Shippo (optional if using a token)
SHIPPO_WEBHOOK_TOKEN=your_random_token # Token you appended as ?token=… (optional if using HMAC)
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 shippo --path /webhooks/shippo
Reference Materials
- references/overview.md - Shippo webhook concepts, events, and payloads
- references/setup.md - Creating webhooks, requesting HMAC, URL tokens, IP allowlist
- references/verification.md - Signature verification details and gotchas