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):
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:
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_urlusing 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_urlhost 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_urlhost.
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.
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.
Make ShipStation webhooks production-ready
Hookdeck adds authentication V1 lacks, rate-limits your fetches, and durably queues every notification
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 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 for free and handle ShipStation webhooks reliably in minutes.