# Guide to ShipStation Webhooks: Features and Best Practices

ShipStation webhooks notify your systems when order and shipping activity happens: a new order arrives, a shipment is created, a fulfillment ships. If you're syncing ShipStation with a store, an ERP, or a notifications service, webhooks are how you react without polling.

This guide focuses on ShipStation's V1 webhooks (the ones on `ssapi.shipstation.com`), which use a thin-payload model and, notably, have no signature verification. We cover the events, the trust model, the rate limits, and the best practices, plus a note on the newer ShipEngine-based V2.

## What are ShipStation webhooks?

A ShipStation V1 webhook is an HTTP POST that carries a thin payload: just a `resource_url` and a `resource_type`. It tells you that something of a given type changed and where to fetch it, not what changed. You then GET the `resource_url` with Basic auth (your API key and secret) to retrieve the actual data.

The trust model is unusual: V1 offers no signature or HMAC. Authenticity rests on the secrecy of your endpoint URL plus the fact that you fetch the real data from an authenticated ShipStation API URL.

## ShipStation V1 webhook features

| Feature | Details |
| --- | --- |
| Host | `ssapi.shipstation.com` |
| Payload | Thin: `resource_url` + `resource_type` |
| Data fetch | GET `resource_url` with HTTP Basic auth (API key/secret) |
| V1 rate limit | 40 requests/min per key (429 + `X-Rate-Limit-Reset`) |
| Signature | None; no secret/HMAC in V1 |
| Configuration | `POST /webhooks/subscribe` or UI (Settings > Integrations > Webhooks) |
| Retry | Undocumented for V1 |
| V2 (ShipEngine) | RSA-SHA256 signature headers + JWKS (different product) |

## V1 events

ShipStation V1 has a fixed set of six webhook events:

| Event | Fires when |
| --- | --- |
| `ORDER_NOTIFY` | A new order is imported |
| `ITEM_ORDER_NOTIFY` | A new order is imported (item-level) |
| `SHIP_NOTIFY` | An order ships |
| `ITEM_SHIP_NOTIFY` | An order ships (item-level) |
| `FULFILLMENT_SHIPPED` | A fulfillment ships |
| `FULFILLMENT_REJECTED` | A fulfillment is rejected |

## Setting up ShipStation V1 webhooks

Subscribe via the API or the UI (Settings > Integrations > Webhooks):

```bash
curl -X POST "https://ssapi.shipstation.com/webhooks/subscribe" \
  -u "$SS_API_KEY:$SS_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "target_url": "https://your-app.example.com/webhook",
    "event": "SHIP_NOTIFY",
    "friendly_name": "Ship notifications"
  }'

```

On delivery you receive a thin payload and fetch the data yourself:

```javascript
app.post("/webhook", express.json(), async (req, res) => {
  res.sendStatus(200); // acknowledge fast

  const { resource_url, resource_type } = req.body;
  // Fetch the actual data with Basic auth, respecting the 40 req/min limit
  enqueue(async () => {
    const auth = Buffer.from(`${process.env.SS_API_KEY}:${process.env.SS_API_SECRET}`).toString("base64");
    const data = await fetch(resource_url, { headers: { Authorization: `Basic ${auth}` } });
    // process data for resource_type
  });
});

```

## Securing ShipStation V1 webhooks

There's no signature to verify in V1. To reduce the risk that comes with an unauthenticated endpoint:

* Keep the target URL secret and hard to guess (include a long random path segment).
* Fetch data only from the `resource_url` using your Basic auth credentials, so even a spoofed ping can't feed you fake data; the real data always comes from an authenticated ShipStation API call.
* Validate the `resource_url` host is a ShipStation domain before fetching, so a forged payload can't point you at an attacker's server.
* Add your own auth in front of the endpoint (a shared secret in the path or a custom header your infrastructure checks).

Some V1 deliveries reportedly include `x-shipengine-rsa-sha256-*` headers, but there's no documented public-key verification path for V1, so don't rely on them.

## ShipStation webhook limitations and pain points

### No signature verification in V1

The Problem: V1 has no HMAC or signature. Any POST to your URL looks like a legitimate ShipStation ping, so you can't cryptographically prove authenticity.

Why It Happens: V1 was designed as a thin notifier where the authenticated `resource_url` fetch is the trust boundary, not a signed payload.

Workarounds:

* Treat the ping as an untrusted trigger and always fetch real data from the authenticated `resource_url`.
* Keep the endpoint URL secret and validate the `resource_url` host.

How Hookdeck Can Help: Hookdeck gives you a dedicated ingestion URL and can enforce its own authentication in front of your endpoint, adding the verification layer V1 lacks.

### Thin payloads plus a 40 req/min limit

The Problem: Every webhook requires a follow-up API call, and the V1 rate limit is just 40 requests per minute per key. A burst of orders can exhaust the limit and back up processing (429 with `X-Rate-Limit-Reset`).

Why It Happens: V1's thin design pushes data retrieval to you, against a tight rate limit.

Workarounds:

* Queue pings and fetch data at a controlled rate under 40 req/min.
* Back off on 429 using `X-Rate-Limit-Reset`, and batch where the API allows.

How Hookdeck Can Help: Hookdeck durably queues pings and can rate-limit forwarding so your fetches stay under ShipStation's 40 req/min ceiling instead of bursting into 429s.

### Undocumented retry behavior

The Problem: V1 retry behavior isn't documented, so you can't assume a failed delivery will be retried. A missed ping may simply be gone.

Why It Happens: ShipStation doesn't publish V1's retry semantics.

Workarounds:

* Persist every ping on receipt and reconcile against the API periodically.
* Don't rely on webhooks as a complete record; treat them as a prompt to sync.

How Hookdeck Can Help: Hookdeck durably stores every delivery and retries to your downstream on a schedule you control, giving V1 the retry and replay guarantees it doesn't document.

### V1 versus V2 confusion

The Problem: ShipStation's newer API V2 is ShipEngine-based, with different events and RSA-SHA256 signature headers (`x-shipengine-rsa-sha256-*`, JWKS-verified). Mixing up the two leads to verifying signatures that V1 doesn't send, or expecting V1 events on V2.

Why It Happens: Two generations of the product coexist with different webhook models.

Workarounds:

* Know which API your integration targets. This guide covers V1 (`ssapi.shipstation.com`).
* If you're on V2/ShipEngine, verify the RSA-SHA256 signature against the JWKS as that product documents.

How Hookdeck Can Help: Hookdeck can ingest either product's webhooks and normalize them, so downstream consumers aren't reasoning about which generation sent an event.

## Best practices

### Treat pings as untrusted triggers

Always fetch the real data from the authenticated `resource_url`, validate its host, and keep the endpoint URL secret with your own auth in front.

### Respect the 40 req/min limit

Queue pings and fetch under the rate limit, backing off on 429 with `X-Rate-Limit-Reset`.

### Acknowledge fast, fetch asynchronously

Return 200 immediately and do the `resource_url` fetch off a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Reconcile, since retries aren't documented

Persist pings and reconcile against the API so an un-retried delivery doesn't leave you out of sync.

### Know your API version

Target V1 (`ssapi.shipstation.com`) deliberately, and use the ShipEngine RSA verification path only if you're on V2.

## Conclusion

ShipStation V1 webhooks are thin by design: a `resource_url` and a `resource_type`, with no signature and an authenticated fetch as the trust boundary. That, plus a 40 req/min limit and undocumented retries, means the reliability and security work is largely yours: treat pings as untrusted triggers, fetch from the authenticated URL, stay under the rate limit, and reconcile because retries aren't guaranteed.

[Hookdeck](https://hookdeck.com) adds the authentication V1 lacks, rate-limits your API fetches, deduplicates, and durably queues every notification at the edge, so your integration processes trustworthy events without bursting into ShipStation's rate limit or losing an un-retried ping.

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