
Security News
The Changelog Podcast: Practical Steps to Stay Safe on npm
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.
shipbob-node-sdk
Advanced tools
First of all there are no official SDKs for ShipBob. I'm just dropping this here, in case it will speed up somebody else getting started using their API.
This library uses the built-in node.js fetch, so you'll want a newer node version with undici support.
This SDK exposes some endpoints not available in the OpenAPI including:
/2.0/receiving-extended/2.0/product/experimental/product :skull:/experimental/receiving :skull:Have a look in the /test folder. You might like more something like PostMan, but you can run and debug these "tests" in VS Code. You need a .env file like this:
# for the PAT (Personal Access Token) login (recommended)
SHIPBOB_API_TOKEN=<redacted>
# for the oAuth login
SHIPBOB_CLIENT_ID=<redacted>
SHIPBOB_CLIENT_SECRET=<redacted>
# for the Web UI scraper (web.shipbob.com) login
SHIPBOB_WEB_UI_EMAIL=email@company.com
SHIPBOB_WEB_UI_PASSWORD=<redacted>
:x: = I haven't needed this
:heavy_check_mark: = implemented
:question: = might add support soon - under investigation
api.getOneShipmentByOrderIdAndShipmentId(...)Turns out the webhooks aren't reliable, so polling is needed to get shipment information.
order_shipped webhook can fire without tracking detailsshipment_delivered webhook may not be sent. Additionally, exceptions (return to sender) have no webhook.api.getOneShipment()api.getShippingMethods()api.getProducts1_0(...)api.createProduct1_0(...)These are not documented on the site yet:
api.getProducts2_0(...)api.createProduct2_0(...)api.updateProducts2_0(...)Kindly note as it's experimental subject to change/removal :skull:
api.getProductsExperimental(...)api.createProductExperimental(...)api.updateProductsExperimental(...)api.listInventory(...)api.getChannels()NOTE: This fails with a web UI scraper account (you need channels_read scope)
api.getFulfillmentCenters()api.getWarehouseReceivingOrder(...)api.getWarehouseReceivingOrderBoxes(...)api.createWarehouseReceivingOrder(...)api.getReceivingExtended(...) (will include this in a recipe that uses SetExternalSync)Kindly note as it's experimental subject to change/removal :skull:
api.experimentalReceivingSetExternalSync(...)NOTE: See the section below How to sync WROs for some guidance.
api.getWebhooks()api.registerWebhookSubscription(...)api.unregisterWebhookSubscription(...)As a personal note. I regret implementing webhooks at all, since I needed to implement polling anyway. There are various gaps in the webhooks. Using their table is perhaps the best way to explain:
| Shipment Status | Status Detail Name | Comment |
|---|---|---|
| Processing | Labeled | I think this maps to "order_shipped" webhook, doesn't always have tracking (or invoice amounts) |
| Completed | Delivered | Does not always fire when delivered (seems to be carrier dependent) |
| Completed | Delivery Exception | No Webhook (need to poll for RTS, etc.) |
| Exception | * | These do fire, but are not useful for delivery |
ie: No webhooks for delivery exceptions ie: Completed -> DeliveryException when carrier needs more details. The shipment_exception webhook only applies to before the shipment leaves their FC (ie: inventory related issue).
Partial rows of table from here: (https://developer.shipbob.com/#shipment-statuses)
This is not part of the API, but instead allows you to follow URI's returned in the API (see tests for examples). They use some kind of HATEOAS, but it's inconsistent across API versions.
next-page: api.getPath<T>(response.headers['next-page'])For making changes to this library locally - use yarn link to test out the changes easily. This is useful if you would like to contribute.
Not really sure anybody will ever need this as many common platforms probably have integrations. There are a couple of other community SDKs. They do not have /2.0/* or /experimental/* endpoints:
NOTE: I did not notice until all this code was written that ShipBob had published an Open API spec :facepunch:. You may have better luck generating your own client. Maybe those generated typings at least belong here.
$ yarn generate:client
The included script using OpenAPI can generate clients in various languages.
You can fake out this library itself, or otherwise mocking the ShipBob API http calls are quite easy to simulate with nock. Here's a way to test creating an order verifying idempotent operation.
// NOTE: nock > 14 with undici is needed to mock underlying "fetch" calls
const CHANNELS_RESPONSE: ChannelsResponse = [{
id: 1,
application_name: 'SMA',
name: 'test',
scopes: []
}];
const nockScope = nock('https://sandbox-api.shipbob.com')
.defaultReplyHeaders({ 'content-type': 'application/json' })
.get('/1.0/channel')
.once()
.reply(200, JSON.stringify(CHANNELS_RESPONSE))
.post('/1.0/order')
.once()
.reply(422, JSON.stringify({
"": [
"Cannot insert order with existing ReferenceId"
]
}))
.get('/1.0/order?ReferenceIds=123')
.once()
.reply(200, JSON.stringify([{
id: 1,
order_number: '18743683',
}]))
;
...
assert.ok(nockScope.isDone(), 'should have completed nock requests');
To replace what could be considered "missing" webhooks, such as WRO completed (Receiving has no webhooks!).
You can follow the section How to sync WROs for a more robust solution. Read on if you are interested in how this works, but also why it won't work!
If you want something more event driven, you can use the emails they send out with an inbound email processor: ie:
// this is done using Mandrill/Mailchimp Inbound mail processor:
for (const event of events) {
const { subject } = event.msg;
switch (subject) {
case 'Your WRO is now complete':
// ie: Your WRO 756713 is now complete and all associated inventory is ready to ship! ...
// https://web.shipbob.com/app/Merchant/#/inventory/receiving/756713/summary ...
const match = /Your WRO (?<wro>\d+) is now complete/i.exec(event.msg.text);
if (match === null || match.groups === undefined || !('wro' in match.groups)) {
throw new Error(`cannot find wro in email '${taskStorageId}'`);
}
const wro = match.groups.wro;
console.log(` Got it! Received WRO# '${wro}'`);
break;
case 'Your box/pallet is now complete!':
console.log(`Ignoring subject: '${subject}'`);
break;
default:
console.log(`Unsupported subject: '${subject}'.`);
break;
}
}
You can publish that as an event or push to a queue and it will act as a "webhook".
NOTE: I discovered after writing the above inbound mail handler that a WRO you create may be split. ie: we created 1 WRO and it was split into 6 more WROs by the ShipBob team, so it's not really possible to link back to your system when that occurs. There's no hierarchical relationship with the split WROs they are creating. I believe UROs (Unidentified Receiving Orders) may have same behaviour. In other words, you will need to implement polling anyway, so adding this is probably not worthwhile.
There is no S2S (server-to-server) oAuth. User intervention is required. There are only helper methods to help with that. You could bypass that with a webscraper (see next section).
oAuthGetConnectUrl() direct an end user to follow the generated URL. Use offline_access as part of scope to get a refresh token.oAuthGetAccessToken() call this with the code query string fragment https://yourname.ngrok.io/#code=...&id_token=... the redirect_uri (and your client Id and secret from ShipBob App)oAuthRefreshAccessToken() call this with the last refresh_token you received, otherwise the same as oAuthGetAccessToken() without code.The method to get/refresh access token both return access_token, so you provide that to createShipBobApi(...) with your application name.
If you create products with your API, you will not be able to see them with an oAuth app. I went down a big rabbit hole here - something to do with different channels. Try not to waste as much time as I did here and avoid using oAuth unless you are building actually an app IMO.
The Web UI has a much broader set of unsupported APIs (ordersapi.shipbob.com, shippingservice.shipbob.com, merchantpricingapi.shipbob.com, etc.). For example, you cannot "patch" a WRO on the public receiving API, but you can somehow with a web login, so it unlocks extra functionality and has a separate rate limit from the API.
You would need to create a ShipBob account and then use those credentials to login with a scraper (see /src/WebScraper.ts). The account can only be logged with one session at a time. Look at AuthScopesWeb type and you can probably work backwards the scopes needed for each API. If you go this route then the peer dependency on puppeteer is not optional.
There is no documentation for these extra APIs - you can look through network tracing in the browser. See unit tests for full example:
// scrape web.shipbob.com website:
const authResponse = await getAccessTokenUI({
email,
password,
});
const missingChannelScope = authResponse.scopes.indexOf('channel_read') === -1;
const webUIAPI = await createShipBobApi(authResponse.accessToken, url, '<unused>', {
skipChannelLoad: missingChannelScope,
});
const inventoryListResponse = await webUIAPI.listInventory({ Ids: [20104984] });
This is a suggested way to track orders from ShipBob. This may be better than using the webhook, sometimes the order_shipped webhook fires with no tracking information.
/1.0/order?HasTracking=true&IsTrackingUploaded=false&startDate=03-25-2025const results = await api.getOrders({
HasTracking: true,
IsTrackingUploaded: false,
StartDate: '03-25-2025',
});
NOTE: PUT/POST to /1.0/shipment not implemented in this library yet.
Syncing WROs (Warehouse Receiving Orders) back to your system has 2 options.
// with this library
const results = await api.getReceivingExtended({
Statuses: 'Completed',
ExternalSync: false,
});
Mark the WRO as synced (this is optional and a alternative option to continuously polling completed WROs that you may or may not know you have already synced)
POST https://sandbox-api.shipbob.com/experimental/receiving/:set-external-sync
{
"ids": [442946],
"is_external_sync": true
}
// with this library
// use Ids from "getReceivingExtended" with ExternalSync: false.
const results = await api.experimentalReceivingSetExternalSync([443001], true);
Note: When initially testing you can remove the statuses from the query params, and you will see the default status of "AwaitingArrival" whenever a WRO is created. There is no sandbox simulation to move these forward.
FAQs
ShipBob API node SDK
We found that shipbob-node-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.

Security News
Experts push back on new claims about AI-driven ransomware, warning that hype and sponsored research are distorting how the threat is understood.

Security News
Ruby's creator Matz assumes control of RubyGems and Bundler repositories while former maintainers agree to step back and transfer all rights to end the dispute.