Gareth Wilson Gareth Wilson

Guide to ShipHero Webhooks: Features and Best Practices

Published


ShipHero webhooks notify your systems about fulfillment activity: an order is allocated, a shipment updates, inventory changes. If you're building on ShipHero, webhooks are how you react to warehouse and order events without polling the GraphQL API.

This guide covers how ShipHero webhooks work, the webhook types, how to verify the x-shiphero-hmac-sha256 header, the delivery behavior, and the best practices for production.

What are ShipHero webhooks?

ShipHero webhooks are HTTP POSTs delivered to a URL you register per webhook type via the GraphQL API. Each is signed with an HMAC-SHA256 of the raw JSON body, base64-encoded, in the x-shiphero-hmac-sha256 header. The key is the app's shared_signature_secret, returned once by the webhook_create mutation.

ShipHero webhook features

FeatureDetails
ConfigurationGraphQL webhook_create mutation (name = webhook type, url, shop_name)
Signature headerx-shiphero-hmac-sha256
Signature schemebase64 HMAC-SHA256 over the raw body, keyed with shared_signature_secret
SecretReturned once by webhook_create
DeduplicationX-Shiphero-Message-ID
Delivery~10s timeout (20s for Generate Label), up to 5 retries per trigger
While disabledEvents are discarded (not queued)
SDKNone

Common webhook types

ShipHero webhook type names are Title Case strings (the name you pass to webhook_create):

TypeFires when
Order AllocatedAn order is allocated
Shipment UpdateA shipment updates
Inventory UpdateInventory changes
Order CanceledAn order is canceled
Tote CompleteA tote is completed
PO UpdateA purchase order updates

Other types include Return Update, Package Added, and Generate Label Webhook. Register one webhook per type; consult ShipHero's webhook reference for the full list.

Setting up ShipHero webhooks

Register via the GraphQL webhook_create mutation, passing the exact webhook type string as name, your url, and shop_name. The mutation returns the shared_signature_secret once, capture it. Note that ShipHero does not queue events while a webhook is disabled; they're discarded.

Securing ShipHero webhooks

Each delivery carries an x-shiphero-hmac-sha256 header: a base64 HMAC-SHA256 of the raw JSON body, keyed with your shared_signature_secret. This is a plain HMAC of the raw body (not the body concatenated with an account id or anything else). Compute over the exact raw bytes and compare in constant time.

const crypto = require("crypto");

const SECRET = process.env.SHIPHERO_SIGNATURE_SECRET; // shared_signature_secret

function verify(rawBody, signature) {
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(rawBody)
    .digest("base64");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-shiphero-hmac-sha256"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge fast (~10s timeout)
  processQueue.add({
    messageId: req.headers["x-shiphero-message-id"], // dedupe on this
    body: JSON.parse(req.body),
  });
});

The same check in Python:

import base64
import hashlib
import hmac
import os

SECRET = os.environ["SHIPHERO_SIGNATURE_SECRET"].encode()


def verify(raw_body: bytes, signature: str) -> bool:
    digest = hmac.new(SECRET, raw_body, hashlib.sha256).digest()
    expected = base64.b64encode(digest).decode()
    return hmac.compare_digest(expected, signature or "")

ShipHero webhook limitations and pain points

Events are discarded while a webhook is disabled

The Problem: ShipHero doesn't queue events for a disabled webhook, they're dropped. A webhook that's disabled (or an endpoint down long enough to be disabled) loses events permanently.

Why It Happens: ShipHero discards rather than buffering for disabled webhooks.

Workarounds:

  • Keep the webhook enabled and the endpoint healthy; reconcile via the GraphQL API for anything you can't afford to miss.

How Hookdeck Can Help: Hookdeck always accepts deliveries and absorbs downstream failures itself, keeping the webhook enabled while it durably queues events, so a downstream outage doesn't drop them.

The secret is returned once

The Problem: webhook_create returns the shared_signature_secret only once. Lose it and you can't verify without recreating the webhook.

Why It Happens: ShipHero shows the secret at creation only.

Workarounds:

  • Capture and store the secret securely when you create the webhook.

How Hookdeck Can Help: Hookdeck verifies at the edge, so the secret lives in one place.

Title Case type names and per-type webhooks

The Problem: Webhook types are Title Case strings (Order Allocated, not order_allocated), and you register one webhook per type, so a typo or wrong casing fails registration or routing.

Why It Happens: ShipHero uses exact Title Case type strings.

Workarounds:

  • Register with the exact type string and route on it; keep a list of the types you use.

How Hookdeck Can Help: Hookdeck consolidates multiple ShipHero webhooks into one ingestion point with routing.

Retries and duplicates

The Problem: Up to 5 retries per trigger (within a ~10s timeout, 20s for Generate Label) means duplicates.

Why It Happens: At-least-once delivery favors eventual delivery.

Workarounds:

  • Dedupe on X-Shiphero-Message-ID and make side effects idempotent.

How Hookdeck Can Help: Hookdeck deduplicates on the message id at the edge. See our guide to webhook idempotency.

Best practices

Verify the plain HMAC over the raw body

Compute base64 HMAC-SHA256 over the raw body with shared_signature_secret and compare in constant time. It's a plain HMAC, nothing concatenated.

Keep webhooks enabled and healthy

Since disabled webhooks drop events, keep the endpoint healthy and reconcile via the API for critical data.

Dedupe on the message id

Use X-Shiphero-Message-ID as an idempotency key across the up-to-5 retries.

Acknowledge fast, process asynchronously

Return 200 within the timeout and defer work to a queue. See why to process webhooks asynchronously.

Make ShipHero webhooks production-ready

Hookdeck verifies x-shiphero-hmac-sha256, deduplicates, and durably queues every fulfillment event

Conclusion

ShipHero webhooks verify with an x-shiphero-hmac-sha256 base64 HMAC over the raw body, keyed with the shared_signature_secret returned once at creation. Use the exact Title Case type names, keep webhooks enabled (disabled ones drop events), dedupe on X-Shiphero-Message-ID, and acknowledge within the timeout.

Hookdeck verifies the signature, keeps webhooks alive by absorbing downstream failures, deduplicates, and durably queues every event at the edge, so your app processes verified, unique fulfillment events without losing any.

Get started with Hookdeck for free and handle ShipHero webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.