App guides

The ShipStation app sends your Swell orders to ShipStation, where you pick, pack and buy labels. When you buy a label, the app adds a shipment to the Swell order with the carrier, tracking number and the items in the package, and the order's fulfillment status updates to match.

The app replaces Swell's built-in ShipStation integration. It also sends order edits, payment and hold changes and cancellations to ShipStation, records partial shipments with their item quantities, and shows a sync status on each order.

WhenWhat the app does
An order is paid, or submitted if you chooseCreates the order in ShipStation with its addresses, items, product options, weights and totals.
An order's shipping details, billing address or items changeSends the order again, unless ShipStation has already shipped or cancelled it.
An order already in ShipStation is paid, or put on or taken off holdSends the order with its new status, unless ShipStation has already shipped or cancelled it.
An order is canceledMarks the order cancelled in ShipStation.
Every 5 minutesWith Send existing orders on, sends up to 5 paid orders that still have items to ship and haven't been sent, such as orders that were open when you set up the app. Sends nothing while Push orders to ShipStation is Only when re-synced manually.
You buy a label in ShipStationCreates a shipment on the Swell order in the live environment, with the carrier, service, tracking number and the items in the package.
You turn on Re-sync on save on an order and save itSends the order again.
Every day at 06:00 UTCChecks the app's webhooks in ShipStation and repairs them, or removes them while the app is switched off.
  • A ShipStation account with API access.
  • Swell's built-in ShipStation integration turned off, because both send orders to ShipStation and record shipments from it. Installing the app offers to turn it off.
  • Shipping weights on your products, so orders reach ShipStation with item weights.

Install ShipStation from the Swell App Store, and set it up in your test environment before the live one.

If the built-in ShipStation integration is on when you install the app, Swell asks how to handle the conflict. Click Disable conflicting integration to turn off the built-in integration and install the app. Ignore conflict installs the app and leaves the built-in integration on. To turn it off later, go to Integrations, open ShipStation and click Deactivate.

1
Copy your API keys

In ShipStation, go to Settings > Account > API Settings and copy the API Key and API Secret.

2
Add them to the app

In Swell, go to Apps > ShipStation > Settings. On the Credentials tab, enter the API key and API secret. On the Order sync tab, choose when orders are sent under Push orders to ShipStation. In the test environment, turn off Send existing orders, unless you want that environment's open orders in ShipStation. On the Shipment sync tab, enter a Webhook secret of 16 or more random characters. Turn on Enable ShipStation sync and click Save settings.

3
Place a test order

In the test environment, place and pay an order with a physical product. Within a few seconds, it appears in ShipStation as Awaiting Shipment, and the Sync status on the order's ShipStation tab is Synced. Its Custom Field 1 in ShipStation is swell:test: followed by the Swell order ID, which tells it apart from a live order with the same number. Shipments don't come back to the test environment, because ShipStation's notifications reach your live environment only, and a label for a test order is never recorded on a live order. To take the test order out of ShipStation, cancel it in Swell.

4
Go live

Settings are saved separately for each environment. In the live environment, repeat step 2 with the same API keys. With Send existing orders on, the live environment's paid orders that are waiting to ship then go to ShipStation, up to 5 every 5 minutes, oldest first.

The app registers its webhooks in ShipStation at its daily check, at 06:00 UTC. To register them now, go to Developer > API keys in the live environment, copy the Store ID and a secret key, and run setup with them:

curl -X POST "https://<store-id>.swell.store/functions/shipstation/setup" \
  -u "<store-id>:<secret-key>"

The response lists your ShipStation stores with their IDs, and its message starts with "ShipStation webhooks are registered". Then buy a label for a paid order in ShipStation. The order in Swell gets a shipment with the tracking number.

The test and live environments can use the same ShipStation account. Setup and the daily check in the test environment never change or remove the live environment's webhooks.

When an order is paid, or submitted if Push orders to ShipStation is set to that, the app creates it in ShipStation. The order is sent with its shipping and billing addresses, the customer's email, the totals, the order notes as internal notes, the gift message and the shipping service the customer chose. Each item is sent with its product and variant name, SKU, quantity, weight, product options such as size or engraving, and the unit price the customer paid, including tax and less discounts.

  • Only physical items are sent. An order with nothing to ship, such as one with only gift cards, isn't sent, and its status is Skipped.
  • Paid orders are Awaiting Shipment in ShipStation, unpaid orders Awaiting Payment and orders on hold On Hold. When an order ShipStation already has is paid, or put on or taken off hold, the app sends its new status, even with Sync order edits off. An order sent when it was submitted moves out of Awaiting Payment when it's paid.
  • The ShipStation order number is the Swell order number, after the Order number prefix if you set one. Orders go to the store in ShipStation store ID, or to your default ShipStation store.
  • The Swell order ID is sent as ShipStation's order key, so sending an order again updates it in ShipStation instead of creating a second one. The ID is also in Custom Field 1, as swell: followed by the ID. In the test environment, the order key is test: followed by the ID and Custom Field 1 is swell:test: followed by the ID, so test orders are separate ShipStation orders from live orders with the same number.
  • Weights are sent in your store's weight unit. Kilograms are converted to grams, because ShipStation doesn't take kilograms.
  • If sending fails, the status is Error and Last error has the reason. When ShipStation is busy or doesn't answer in time, Swell also sends the order again.

With Send existing orders on, every 5 minutes the app sends up to 5 paid, open orders that still have items to ship and have no sync status, oldest first. After you set up the app, this sends the orders that were already waiting to ship, up to 60 an hour, until none are left. It also sends any other paid order the app hasn't sent, for example one paid while Enable ShipStation sync was off. Orders whose send failed aren't sent again by this check. While Push orders to ShipStation is Only when re-synced manually, this check sends nothing.

With Sync order edits on, an order is sent again when its shipping details, billing address or items change in Swell. With Sync cancellations on, a canceled order is marked cancelled in ShipStation, and its status is Canceled. Both apply only to orders ShipStation already has, as do payment and hold changes.

An edit or a payment or hold change that leaves the order as ShipStation already has it isn't sent. That includes the item update Swell makes when the app records a shipment, so recording a label doesn't send the order again.

Before it sends an edit, a payment or hold change or a cancellation, the app checks the order's status in ShipStation. ShipStation doesn't accept changes to orders it has shipped or cancelled, so those changes aren't sent. The status is then Skipped, and Last error gives the order's ShipStation status. Canceling an order that's already cancelled in ShipStation sets the status to Canceled.

When you buy a label, ShipStation notifies the app, and the app reads the shipment from ShipStation with your API keys. It finds the Swell order by its order key, and creates a Swell shipment with the carrier, service, tracking number, ship-to address and the items and quantities in the package. Swell then updates the order's fulfillment status. Shipments come back to the live environment only.

  • If the order key doesn't match, the app looks the order up by its order number without the prefix, and uses it only if its ShipStation order ID is the one the app saved when it sent the order. Shipments for test orders and for other sales channels' orders are ignored.
  • If a large batch of labels doesn't finish in time, the app records what it can and answers with an error, so ShipStation sends the notification again and the app continues where it stopped.
  • When you ship an order in parts, each label becomes its own Swell shipment with only its items. The order is fulfilled when every item has shipped.
  • Items are matched by order item, or by SKU. Quantities never go past what's still to ship in Swell, so a shipment for items that are already fulfilled in Swell isn't recorded.
  • A shipment that ShipStation reports twice is recorded once.
  • When a notification lists a voided label, the app cancels its Swell shipment. A replacement label for the same items in that notification is recorded too.
  • Carriers and services are saved by name, such as UPS and UPS Ground.
  • The order's ShipStation tab shows Shipments received and Last shipment received.
  • With Create shipments from ShipStation off, the app ignores ShipStation's notifications.

Each order's ShipStation tab shows its Sync status, and the order list has a ShipStation column with the same value.

Sync statusMeaning
No statusThe app hasn't sent the order.
SyncedShipStation accepted the order. Last synced shows when.
ErrorThe last send failed. Last error has the reason.
SkippedThe order wasn't sent because it has nothing to ship, or because ShipStation has already shipped or cancelled it. Last error says which.
CanceledThe cancellation was sent to ShipStation.

To send an order again, for example after you fix the cause of an error, open its ShipStation tab, turn on Re-sync on save and save the order. The switch turns itself off when the send is done. Re-sync on save sends the order even if it was never sent, and it shows only while Enable ShipStation sync is on.

ShipStation notifies the app about shipments through two webhooks in your ShipStation account, one for whole orders and one for partial shipments. They're named swell-<store-id>-SHIP_NOTIFY and swell-<store-id>-ITEM_SHIP_NOTIFY. The app registers them when you run setup, and its daily check at 06:00 UTC registers any that are missing and repairs any that point at the wrong address. While Enable ShipStation sync is off, the daily check removes them. These are the live environment's webhooks. The test environment registers none while Callback URL override is empty, and setup and the daily check in one environment never change the other environment's webhooks.

To see your ShipStation stores, their IDs and the webhooks without changing anything, run setup without -X POST:

curl "https://<store-id>.swell.store/functions/shipstation/setup" \
  -u "<store-id>:<secret-key>"

Set ShipStation store ID before the webhooks are registered, because the webhooks are limited to that store when they're created. If you change it later, remove the webhooks as described under Uninstalling, and run setup again.

WhereWhat it's for
Apps > ShipStation > SettingsThe API keys, store ID, when orders are sent, the order number prefix, existing orders, edits, cancellations, the webhook secret and shipment sync.
The order listA ShipStation column with each order's sync status, and a ShipStation errors tab with the orders whose last send failed.
An order's ShipStation tabThe sync status, ShipStation order number and ID, when it was last synced, the shipments received and when the last one arrived, the last error, the order key and Re-sync on save.
An order's shipmentsThe shipments the app created from ShipStation labels, with the carrier, service and tracking number.
IntegrationsSwell's built-in ShipStation integration, which must stay deactivated while you use the app. Turning it on while the app is installed shows a conflict.

Settings are in Apps > ShipStation > Settings and are saved separately for each environment.

SettingDescriptionDefault
Enable ShipStation syncTurns the app on. While it's off, no orders are sent, shipment notifications are ignored, and the daily check removes the environment's webhooks from ShipStation.Off
API keyFrom ShipStation Settings > Account > API Settings. Required.None
API secretIssued with the API key. Regenerating the key in ShipStation invalidates both. Masked on the settings page. Required.None
ShipStation store IDOptional. Sends orders to this ShipStation store and limits the webhooks to it. Setup lists your stores and their IDs.Your default store
Push orders to ShipStationWhen the order is paid, When the order is submitted, or Only when re-synced manually.When the order is paid
Order number prefixOptional. Added before the Swell order number in ShipStation, such as SW-.None
Send existing ordersEvery 5 minutes, sends up to 5 paid orders that still have items to ship and haven't been sent. Has no effect while Push orders to ShipStation is Only when re-synced manually.On
Sync order editsSends an order again when its shipping details, billing address or items change. Payment and hold changes are sent either way.On
Sync cancellationsMarks an order cancelled in ShipStation when it's canceled in Swell.On
Webhook secretAny random string of 16 characters or more, added to the webhook address. The webhooks aren't registered without it. Masked on the settings page.None
Callback URL overrideReplaces the address ShipStation sends notifications to. Leave it empty.None
Create shipments from ShipStationCreates Swell shipments from ShipStation's shipment notifications.On
Accept test webhook payloadsFor development only. Keep it off.Off
  • Shipments come back to the live environment only, because ShipStation's notifications reach the app's live address. In the test environment, orders are sent to ShipStation, but labels don't come back.
  • Sending an order again replaces it in ShipStation, so changes you made to the order in ShipStation can be lost.
  • Edits, payment and hold changes and cancellations are sent only for orders ShipStation already has. An order whose first send failed is sent again only with Re-sync on save.
  • The app reads up to 2,000 shipments from each ShipStation notification. If ShipStation reports more at once, the rest aren't recorded in Swell, and the logs say which pages weren't read.
  • Unless a voided label and its replacement are in the same notification, the app learns that a label was voided only when a later ShipStation notification lists it. To replace a voided label, cancel its shipment in Swell before you buy the new label. Otherwise the order has nothing left to ship, and the new label isn't recorded.
  • The app doesn't show ShipStation rates at checkout, sync stock levels or handle returns.

Send errors are shown in Last error on the order's ShipStation tab, and the ShipStation errors tab in the order list lists the orders whose last send failed. The app also logs each order it sends and each shipment it records or can't record. In the Swell dashboard, the logs are under Developer > Console > Logs.

Check that Enable ShipStation sync is on, that the API keys are set, and that the order is paid, or submitted, as Push orders to ShipStation requires. Orders go to the store in ShipStation store ID, so look in that store. If the status is Error or Skipped, Last error says why. If the order has no status, check the logs. To send the order again, turn on Re-sync on save and save it.

ShipStation didn't accept the API keys. Copy them again from Settings > Account > API Settings in ShipStation. If you regenerated the key, enter both new values.

Add a shipping address to the order, then turn on Re-sync on save and save it.

ShipStation has shipped or cancelled the order, so the edit, payment or hold change, cancellation or re-sync wasn't sent. Make the change in ShipStation.

The order has no physical items, for example only gift cards, so there's nothing to ship.

ShipStation took too long to answer, and Swell sends the order again. If the status stays Error, turn on Re-sync on save and save the order.

Shipments come back to the live environment only. Check that Enable ShipStation sync and Create shipments from ShipStation are on, and run setup to check the webhooks, or with -X POST to repair them. If ShipStation store ID is set, the label must be in that store. The label must be for an order sent from the live environment, by the app or by Swell's built-in integration. A shipment isn't recorded when the order has nothing left to ship in Swell, for example after you added the shipment yourself. The logs show each shipment the app ignored or couldn't record and why.

Neither the ShipStation shipment nor the Swell order has a street address and a 2-letter country code. Add the shipment to the order in Swell yourself.

Enter the API key, API secret and Webhook secret in the app settings, save, and run setup again.

Setup couldn't work out the address ShipStation sends notifications to. Run setup again in the live environment. If the message stays, email support@swell.is.

You ran setup in the test environment, where shipments don't come back. Run setup in the live environment.

Setup or the daily check couldn't tell whether it ran in the test or live environment, so it didn't change any webhooks. Run setup again. If the message stays, email support@swell.is.

Uninstalling the app stops sending orders and recording shipments, and removes the ShipStation tab, the ShipStation column and the ShipStation errors tab from your orders. Uninstalling applies to the environment you are in, so remove the app from the test and live environments separately.

Swell doesn't tell an app when it's uninstalled, so the app can't remove its webhooks from ShipStation then. Before you uninstall it from the live environment, remove them with setup:

curl -X POST "https://<store-id>.swell.store/functions/shipstation/setup" \
  -u "<store-id>:<secret-key>" -H "Content-Type: application/json" \
  -d '{"action": "remove_webhooks"}'

You can also turn off Enable ShipStation sync in the live environment and uninstall after the next daily check, at 06:00 UTC, which removes them. In the test environment, setup and the daily check remove only that environment's webhooks, never the live environment's.

Orders and shipments in ShipStation stay there, and the Swell shipments the app created stay on your orders. The app's settings and the ShipStation fields on your orders are kept, and come back if you reinstall the app.

FunctionRuns onWhat it does
order-pushorder.submitted and order.paidSends the order on the event chosen in Push orders to ShipStation. On order.paid, also sends the new status of an order that was sent unpaid.
order-updateorder.updatedSends the order again when paid or hold changed, when shipping, billing or items changed and Sync order edits is on, or when Re-sync on save was turned on. Skips a send that would leave the ShipStation order unchanged, except for Re-sync on save.
order-cancelorder.canceledSends the order with the cancelled status.
order-backfillEvery 5 minutesSends up to 5 paid orders that aren't canceled or closed, have items to ship and have no sync status, oldest first. Sends nothing when Push orders to ShipStation is Only when re-synced manually.
shipstation-webhookPublic POST route, called by ShipStation's SHIP_NOTIFY and ITEM_SHIP_NOTIFY webhooksReads the shipments from ShipStation and creates or cancels Swell shipments.
setupPrivate GET and POST routeGET checks the API keys and lists the ShipStation stores and webhooks. POST registers or repairs the webhooks, or removes them with action remove_webhooks.
resyncPrivate POST routeSends one order, or up to 10 orders with a given sync status, newest first.
webhook-reconcileEvery day at 06:00 UTCRegisters or repairs the environment's webhooks, or removes them while the app is switched off.

The app stores its fields on orders under $app.shipstation: sync_status, order_key, shipstation_order_id, shipstation_order_number, last_synced_at, last_error (up to 500 characters), last_webhook_at, shipments_count, payload_hash (a digest of the last order sent, used to skip updates that change nothing) and resync_requested. On the shipments it creates, it stores shipment_id (the ShipStation shipment ID), batch_number and voided.

Orders are created and replaced with ShipStation's POST /orders/createorder. orderKey is the Swell order ID in live, lineItemKey is the order item ID, advancedOptions.source is Swell and advancedOptions.customField1 is swell: plus the orderKey. An item's unitPrice is its price plus per-unit tax, less the per-unit discount.

The environment comes from req.logParams.environment_id, which is test in the test environment and empty in live. Outside live, orderKey is the environment, a colon and the order ID, such as test:<order-id>, and webhooks are named swell-<store-id>.<environment>-<EVENT> and registered only with a Callback URL override. When the environment can't be read, setup and the daily check don't change any webhooks.

The webhooks point at https://<store-id>.swell.store/functions/<app ObjectId>/shipstation-webhook, because a public call by the app's string ID resolves only with a Swell API key. Setup looks the ObjectId up in /:functions. The address ends with ?secret= and the webhook secret, but Swell passes a POST's body and not its query string, so the secret doesn't arrive on real notifications. The route authenticates a notification by fetching its resource_url itself with your API keys, only from ssapi.shipstation.com or a numbered host such as ssapi12.shipstation.com, only for a /shipments listing, and with includeShipmentItems=True and pageSize=500. A secret that arrives and doesn't match is refused with a 401, and inline shipments under Accept test webhook payloads require it.

Shipments are matched to an order by orderKey when the key belongs to the environment, then by orderNumber without the prefix when the shipment's orderId equals the order's shipstation_order_id, and to items by lineItemKey, then by SKU. A shipment that matches no order is counted as unmatched and acknowledged. Voided labels are handled first and return their quantities, quantities are capped at each item's quantity_deliverable, and shipments already recorded are found by $app.shipstation.shipment_id. The route reads up to 4 pages of 500 shipments per notification. It returns a 503, so ShipStation sends the notification again, when it runs out of time before reading every page or recording every shipment, or when a shipment fails with a transient error.

ShipStation calls time out after 4 seconds, and the status check before an edit or cancellation after 2. A 429 with a Retry-After of 2 seconds or less is waited out once. Timeouts, network errors, 429 and 5xx responses fail the event so Swell retries it. Other errors are recorded on the order and not retried.

To send orders again from the CLI, call resync with an order_id, or with a sync_status to send up to 10 orders with that status. Add --live for the live environment:

swell api post /functions/shipstation/resync --body '{"sync_status": "error"}'

Swell builds and maintains the ShipStation app. For help with the app, email support@swell.is. For questions about your ShipStation account, carriers or labels, contact ShipStation.