# Guide to ShipBob Webhooks: Features and Best Practices

ShipBob webhooks notify your systems about fulfillment activity: an order ships, a shipment is delivered, tracking updates, a return is created. If you're building on ShipBob's fulfillment API, webhooks are how you react in real time without polling.

This guide covers how ShipBob webhooks work, the topics you'll subscribe to, how to verify signatures (ShipBob uses the Standard Webhooks format), the retry behavior, and the best practices for production.

## What are ShipBob webhooks?

ShipBob webhooks are HTTP POSTs delivered to a URL you subscribe. The signature scheme is the [Standard Webhooks](https://www.standardwebhooks.com/) / Svix format (the docs don't name it, but the fields are identical): `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers.

Because it's Standard Webhooks, you verify with the familiar HMAC-over-`id.timestamp.body` scheme, using the Svix or `standardwebhooks` libraries.

## ShipBob webhook features

| Feature | Details |
| --- | --- |
| Configuration | `POST /2026-01/webhook` with `{ topics: [...], url }` |
| Scopes | `webhooks_write` plus the read scope per topic (`orders_read`, `fulfillments_read`, ...) |
| Signature headers | `webhook-id`, `webhook-timestamp`, `webhook-signature` |
| Signature scheme | base64 HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{body}` |
| Signing secret | `whsec_<base64>` (strip the prefix, base64-decode the remainder) |
| Retries | immediately, 5s, 5m, 30m, 2h, 5h, 10h, 10h |
| Acknowledgement | 2xx within ~15s |
| Auto-disable | After ~5 days of failures |
| SDK | None (use `svix` / `standardwebhooks`) |

## Common topics

ShipBob's current topics use dotted names:

| Topic | Fires when |
| --- | --- |
| `order.shipped` | An order ships |
| `order.shipment.delivered` | A shipment is delivered |
| `order.shipment.tracking.updated` | Tracking updates for a shipment |
| `order.shipment.exception` | A shipment hits an exception |
| `return.created` | A return is created |
| `wro.created` | A warehouse receiving order is created |

Legacy 1.0/2.0 topics used underscores (`order_shipped`, `shipment_delivered`); the current API uses the dotted names above. Subscribe to the topics you process, and consult ShipBob's webhook reference for the full list.

## Setting up ShipBob webhooks

Subscribe via `POST /2026-01/webhook` with `{ topics: [...], url }`. You need the `webhooks_write` scope plus the read scope for each topic (`orders_read`, `fulfillments_read`, `returns_read`, `receiving_read`, `billing_read`). The `2026-01` date-versioned path is current; the legacy `2.0` API used `POST /2.0/webhook` with `{ topic, subscription_url }`.

## Securing ShipBob webhooks

Each delivery is signed with HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{body}`, keyed with the base64-decoded portion of your `whsec_` secret; `webhook-signature` holds space-delimited `v1,<sig>` entries. There's no official ShipBob SDK, so use the `svix` (or `standardwebhooks`) library:

```javascript
const { Webhook } = require("svix");

const wh = new Webhook(process.env.SHIPBOB_WEBHOOK_SECRET); // whsec_...

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  try {
    const payload = wh.verify(req.body, {
      "webhook-id": req.headers["webhook-id"],
      "webhook-timestamp": req.headers["webhook-timestamp"],
      "webhook-signature": req.headers["webhook-signature"],
    });
    res.sendStatus(200); // acknowledge within ~15s
    processQueue.add(payload); // process asynchronously
  } catch {
    res.sendStatus(400); // verification failed
  }
});

```

The same check in Python:

```python
from standardwebhooks import Webhook

wh = Webhook(os.environ["SHIPBOB_WEBHOOK_SECRET"])  # whsec_...

@app.post("/webhook")
def webhook():
    try:
        payload = wh.verify(request.get_data(), dict(request.headers))
    except Exception:
        return "", 400
    # process asynchronously
    return "", 200

```

Verify against the raw, unmodified request body.

## ShipBob webhook limitations and pain points

### Standard Webhooks mechanics

The Problem: The `whsec_` secret must be base64-decoded (after the prefix), the signature is over `id.timestamp.body`, and rotation yields multiple `v1,<sig>` entries. Hand-rolling any of it wrong fails verification.

Why It Happens: These are Standard Webhooks/Svix details.

Workarounds:

* Use the `svix` or `standardwebhooks` library rather than implementing the HMAC yourself.

How Hookdeck Can Help: Hookdeck verifies Standard Webhooks signatures at the edge, so your app receives pre-verified events. See our [guide to Svix webhooks](/webhooks/platforms/guide-to-svix-webhooks-features-and-best-practices).

### Dotted vs underscore topics across versions

The Problem: Current topics are dotted (`order.shipped`); legacy 1.0/2.0 used underscores (`order_shipped`). Subscribing with the wrong form (or on the wrong API version) means you receive nothing.

Why It Happens: ShipBob changed the topic naming and API path across versions.

Workarounds:

* Use the `2026-01` API with dotted topics, and confirm the exact topic strings against the current reference.

How Hookdeck Can Help: Hookdeck's filters route on the fields you actually receive, so version differences are handled in one place.

### The retry ladder and auto-disable

The Problem: Failed deliveries retry on a fixed ladder (immediately, 5s, 5m, 30m, 2h, 5h, 10h, 10h), and the subscription auto-disables after ~5 days of failures. Duplicates arrive along the way.

Why It Happens: At-least-once delivery plus pruning of persistently failing endpoints.

Workarounds:

* Acknowledge with a 2xx within ~15s so transient issues don't accumulate; dedupe on `webhook-id`; monitor and re-enable if disabled.

How Hookdeck Can Help: Hookdeck accepts every delivery, deduplicates, and retries to your downstream on a schedule you control, so the ladder isn't your only safety net and the subscription stays active. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

## Best practices

### Verify with a Standard Webhooks library

Use `svix` / `standardwebhooks` to handle the base64 secret, the `id.timestamp.body` construction, and rotation, over the raw body.

### Use the current dotted topics

Subscribe on the `2026-01` API with dotted topic names.

### Acknowledge within ~15 seconds, process asynchronously

Return 2xx quickly and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Dedupe on `webhook-id` and monitor status

Dedupe so retries don't double-process, and watch for auto-disable after sustained failures.

## Conclusion

ShipBob webhooks use the Standard Webhooks format: verify the `webhook-signature` over `id.timestamp.body` with the base64-decoded `whsec_` secret, subscribe with the current dotted topics on the `2026-01` API, and handle the retry ladder plus ~5-day auto-disable. With no official SDK, the `svix` / `standardwebhooks` libraries do the verification.

[Hookdeck](https://hookdeck.com) verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique fulfillment events.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle ShipBob webhooks reliably in minutes.