Agent skill

ShipHero Webhooks Skill

Receive and verify ShipHero webhooks. Use when setting up ShipHero webhook handlers, debugging signature verification (x-shiphero-hmac-sha256), or handling fulfillment events like Order Allocated, Shipment Update, Inventory Update, Order Canceled, and Return Update.

Install this skill

npx skills add hookdeck/webhook-skills --skill shiphero-webhooks


When to Use This Skill

  • How do I receive ShipHero webhooks?
  • How do I verify ShipHero webhook signatures?
  • How do I handle Order Allocated, Shipment Update, or Inventory Update events?
  • Why is my ShipHero webhook signature verification failing?
  • How do I register a ShipHero webhook with the webhook_create mutation?

Verification (core)

ShipHero signs each webhook with HMAC-SHA256 over the raw JSON request body, base64-encoded, sent in the x-shiphero-hmac-sha256 header. The key is the app's shared_signature_secret, returned once by the webhook_create mutation. Verify by recomputing base64(HMAC-SHA256(rawBody, secret)) and comparing timing-safe against the header. Pass the raw body — parsing JSON first will break the signature. This is a plain HMAC of the raw body (NOT payload-concatenated-with-account-id, NOT Standard Webhooks).

There is no topic header — dispatch on the webhook_type field inside the (verified) payload. X-Shiphero-Message-ID is a unique per-delivery id for deduplication.

Node:

const crypto = require('crypto');

function verifyShipHeroWebhook(rawBody, hmacHeader, secret) {
  if (!hmacHeader) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
  try {
    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(hmacHeader));
  } catch {
    return false;
  }
}

Python:

import hmac, hashlib, base64

def verify_shiphero_webhook(raw_body: bytes, hmac_header: str, secret: str) -> bool:
    if not hmac_header:
        return False
    expected = base64.b64encode(
        hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(expected, hmac_header)

Respond quickly: ShipHero uses a ~10s timeout (20s for Generate Label) and retries up to 5 times per trigger. Respond 2xx with body {"code": "200", "Status": "Success"} and process work asynchronously if slow. Note: ShipHero does not queue events while a webhook is disabled — they are discarded.

For complete handlers with route wiring, event dispatch, and tests, see:

Common Webhook Types

ShipHero webhook type names are Title Case strings. The registered name (in webhook_create) matches the webhook_type field in the payload.

Webhook TypeTriggered When
Order AllocatedInventory is allocated to an order
Shipment UpdateAn order ships (tracking, carrier, packages)
Inventory UpdateOn-hand / available inventory changes
Order CanceledAn order is canceled
PO UpdateA purchase order changes state
Return UpdateA return (RMA) is created or updated
Tote CompleteA pick tote is completed
Package AddedA package is added to a shipment

For the full list (Inventory Change, Order Deallocated, Order Packed Out, Capture Payment, Generate Label, Print Barcode, Tote Cleared, Automation Rules, Shipment ASN, Work Order Status Update), see references/overview.md and ShipHero Webhooks docs.

Environment Variables

SHIPHERO_WEBHOOK_SECRET=your_shared_signature_secret   # shared_signature_secret from webhook_create

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 shiphero --path /webhooks/shiphero

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 4, 2026

View on GitHub →