Netsuite Skinny

View as Markdown

Overview

This document specifies the technical requirements for building a bidirectional integration between NetSuite (ERP / OMS) and ShipBob (3PL / fulfillment) for Skinny Mixes.

The integration consists of three primary data flows:

FlowDirectionFrequency
Order SyncNetSuite → ShipBobEvery 15 minutes
Tracking SyncShipBob → NetSuiteEvery 15–30 minutes
Product SyncNetSuite → ShipBobEvery 60 minutes

Note on order delay: If Skinny Mixes experiences a high volume of order cancellations within the first hour of order creation, add a 1-hour order delay before syncing orders to ShipBob. It may be preferable to implement this delay in the Shopify ↔ NetSuite layer before orders even reach this integration.

Sync 1: Order Sync (NetSuite → ShipBob)

Overview

When a Sales Order in NetSuite reaches Pending Fulfillment status, the integration should create a corresponding order in ShipBob via the Orders API.

Trigger Condition

NetSuite Sales Order status = "Pending Fulfillment"

Frequency

  • Recommended for Skinny Mixes: Every 15 minutes
  • Alternatives: Every 30 minutes or every 60 minutes

Optional: Order Delay

If Skinny Mixes has a high cancellation rate within the first hour of order creation, add a 1-hour delay before syncing to ShipBob. This can be implemented in either:

  • The Shopify ↔ NetSuite sync (preferred), or
  • This integration layer by filtering out orders created less than 60 minutes ago

API Endpoint

POST https://sandbox-api.shipbob.com/2026-01/order

Required Headers:

Authorization: Bearer <YOUR_API_TOKEN>
shipbob_channel_id: 168384

Field Mapping

ShipBob FieldNetSuite SourceNotes
reference_idSales Order internal IDUsed as idempotency key — must be unique per channel
order_numberSales Order # (e.g., SO2)User-friendly display number
typeHardcoded"DTC" for direct-to-consumer; "B2B" for wholesale
shipping_methodShipping method on SOMap to ShipBob ship options (e.g., "Standard", "Expedited")
sales_channelOrder source"shopify" or "faire" or omit for default
recipient.nameShip-to contact name
recipient.address.address1Ship-to address line 1
recipient.address.address2Ship-to address line 2
recipient.address.cityShip-to city
recipient.address.stateShip-to state (2-letter code)
recipient.address.countryShip-to country (2-letter code)
recipient.address.zip_codeShip-to zip
recipient.emailCustomer email
recipient.phone_numberCustomer phone
products[].reference_idItem SKUMust match a product already created in ShipBob
products[].nameItem name
products[].quantityOrdered quantity

Example Request

1POST https://sandbox-api.shipbob.com/2026-01/order
2Authorization: Bearer <YOUR_API_TOKEN>
3shipbob_channel_id: 168384
4
5{
6 "shipping_method": "Standard",
7 "recipient": {
8 "name": "John Doe",
9 "address": {
10 "address1": "100 Nowhere Blvd",
11 "address2": "Suite 100",
12 "city": "Chicago",
13 "state": "IL",
14 "country": "US",
15 "zip_code": "60607"
16 },
17 "email": "john@example.com",
18 "phone_number": "555-555-5555"
19 },
20 "products": [
21 {
22 "name": "Sugar Free Cookie Butter Syrup",
23 "reference_id": "2201361",
24 "quantity": 1
25 }
26 ],
27 "reference_id": "123456",
28 "order_number": "SO2",
29 "type": "DTC",
30 "sales_channel": "shopify"
31}

Important Notes

  • reference_id is the idempotency key. If you submit two orders with the same reference_id + shipbob_channel_id, ShipBob will return a 422 error. Use the NetSuite Sales Order internal ID.
  • products[].reference_id must match the SKU of a product already created in ShipBob. If the product does not exist, the order will go into ImportReview status.
  • Pass "sales_channel": "faire" for Faire marketplace orders, or "shopify" for Shopify orders.

Order Cancellation

Orders can be cancelled in ShipBob if they have not yet been Picked.

POST https://sandbox-api.shipbob.com/2026-01/order/{orderId}/cancel

To cancel a specific shipment within a split order:

POST https://sandbox-api.shipbob.com/2026-01/shipment/{shipmentId}/cancel

Once a shipment reaches Picked status, it cannot be cancelled via API.


Sync 2: Tracking Sync (ShipBob → NetSuite)

Overview

When an order ships from ShipBob, retrieve the tracking number and carrier information and create an Item Fulfillment record in NetSuite.

Frequency

  • Recommended for Skinny Mixes: Every 15–30 minutes

Workflow

1. Poll ShipBob for shipped orders with unsynced tracking
2. For each order with tracking, loop through all shipments
3. Sync each shipment's tracking number + carrier to NetSuite
4. Mark the shipment as tracking-uploaded in ShipBob

Step A: Poll for Shipped Orders

GET https://sandbox-api.shipbob.com/2026-01/order?HasTracking=true&IsTrackingUploaded=false&Limit=250&Page=1
Authorization: Bearer <YOUR_API_TOKEN>
shipbob_channel_id: 168384

Key Query Parameters:

ParameterValueDescription
HasTrackingtrueOnly returns orders where at least one shipment has been labeled
IsTrackingUploadedfalseOnly returns orders not yet synced to your system
Limit250Max page size
Page1, 2, …Paginate through results using the total-pages response header

Check the total-count or total-pages response header to determine if pagination is needed.

Step B: Handle Split Shipments

An order may have multiple shipments if:

  • Inventory is stocked at multiple ShipBob fulfillment centers
  • Items don’t fit in a single box due to size/weight

Always loop through order.shipments[] and sync each tracking number individually.

1{
2 "id": 123456,
3 "reference_id": "ORDER-001",
4 "status": "Partially Fulfilled",
5 "shipments": [
6 {
7 "id": 789101,
8 "status": "Completed",
9 "tracking": {
10 "number": "1Z999AA10123456784",
11 "carrier": "UPS"
12 }
13 },
14 {
15 "id": 789102,
16 "status": "Processing",
17 "tracking": null
18 }
19 ]
20}

Step C: Create Item Fulfillment in NetSuite

For each ShipBob shipment with tracking, create a NetSuite Item Fulfillment record linked to the originating Sales Order.

Suggested NetSuite Item Fulfillment field mapping:

NetSuite FieldShipBob Source
Sales Order (link)Matched via order.reference_id = NetSuite SO internal ID
Tracking Numbershipment.tracking.number
Carriershipment.tracking.carrier
Ship Dateshipment.last_update_at
StatusShipped

Step D: Mark Tracking as Synced

After successfully creating the Item Fulfillment in NetSuite, mark the ShipBob shipment as uploaded to prevent re-processing on the next poll cycle.

1POST https://sandbox-api.shipbob.com/2026-01/shipment:batchUpdateTrackingUpload
2Authorization: Bearer <YOUR_API_TOKEN>
3shipbob_channel_id: 168384
4
5{
6 "shipment_ids": [789101, 789102],
7 "is_tracking_uploaded": true
8}

Webhook Option (Alternative to Polling)

For real-time tracking updates, subscribe to the order.shipped ShipBob webhook event:

  1. Via Dashboard: Integrations > Webhooks > Add Subscription
  2. Via API: POST /webhook

ShipBob webhooks use exponential backoff and retry delivery for up to 24 hours. Recommendation: Use webhooks for real-time updates with polling as a fallback safety net.

Reference: https://developer.shipbob.com/webhooks


Sync 3: Product Sync (NetSuite → ShipBob)

Overview

Sync product/variant data from NetSuite to ShipBob to ensure SKUs exist in ShipBob before orders are submitted. Products must exist in ShipBob before orders referencing their SKUs can be fulfilled.

Frequency

  • Recommended: Every 60 minutes (adjust based on catalog change frequency)

API Endpoint

POST https://sandbox-api.shipbob.com/2026-01/product
Authorization: Bearer <YOUR_API_TOKEN>
shipbob_channel_id: 168384

Field Mapping

ShipBob FieldNetSuite SourceNotes
nameItem nameProduct display name
type_idHardcoded1 = standard product
variants[].nameItem name / variant name
variants[].skuItem SKUThis is the reference_id used in order sync
variants[].barcodes[].valueUPC / BarcodeOptional but recommended
variants[].packaging_requirement_idHardcoded or mapped1 = standard
variants[].packaging_material_type_idHardcoded or mapped1 = standard
variants[].lot_information.is_lotNetSuite lot tracking flagSet true if item is lot-tracked

Example Request

1POST https://sandbox-api.shipbob.com/2026-01/product
2Authorization: Bearer <YOUR_API_TOKEN>
3shipbob_channel_id: 168384
4
5{
6 "name": "Sugar Free Marshmallow Sauce",
7 "type_id": 1,
8 "variants": [
9 {
10 "name": "Sugar Free Marshmallow Sauce",
11 "sku": "2401297",
12 "barcodes": [
13 {
14 "value": "197874002978"
15 }
16 ],
17 "packaging_requirement_id": 1,
18 "packaging_material_type_id": 1,
19 "lot_information": {
20 "is_lot": true
21 }
22 }
23 ]
24}

Product Sync Logic

ConditionAction
Product SKU does not exist in ShipBobPOST /product to create it
Product SKU already exists in ShipBobSkip or PATCH /product/{id} to update name/barcode
NetSuite item is inactiveDo not sync / skip

API Reference

OperationMethodEndpoint
Get channel IDGET/2026-01/channel
Create productPOST/2026-01/product
Get productsGET/2026-01/product
Create orderPOST/2026-01/order
Get orders (with tracking)GET/2026-01/order?HasTracking=true&IsTrackingUploaded=false
Get order by IDGET/2026-01/order/{orderId}
Get order by reference IDGET/2026-01/order?ReferenceIds={referenceId}
Cancel orderPOST/2026-01/order/{orderId}/cancel
Cancel shipmentPOST/2026-01/shipment/{shipmentId}/cancel
Mark tracking uploadedPOST/2026-01/shipment:batchUpdateTrackingUpload
Simulate shipment (sandbox only)POST/2025-07/simulate/shipment

Full API reference: https://developer.shipbob.com


Error Handling

ScenarioHandling
422 — Duplicate reference_idOrder already exists in ShipBob. Skip and log. Verify by querying GET /order?ReferenceIds=...
422 — Invalid addressLog error and flag the NetSuite order for manual review
Product SKU not found in ShipBobOrder enters ImportReview. Ensure product sync runs before order sync or on-demand create the product
Order stuck in Exception or OnHoldAlert the Skinny Mixes operations team for manual intervention
429 — Rate limit exceededImplement retry with exponential backoff; do not exceed 150 req/min
Network timeoutImplement retry with idempotency: re-submitting with the same reference_id is safe
ShipBob shipment status PickedCannot cancel; alert ops team

Testing Checklist

Sandbox Testing Steps

1. Verify sandbox access

  • Log in to ShipBob sandbox at https://webstage.shipbob.dev
  • Add a test credit card payment method

2. Get channel ID

  • GET /2026-01/channel → confirm channel ID 168384

3. Create a test product

  • POST /2026-01/product with a Skinny Mixes SKU
  • Verify product appears in ShipBob dashboard

4. Create a test order

  • POST /2026-01/order using the product SKU from above
  • Confirm reference_id maps to a NetSuite Sales Order internal ID
  • Confirm order_number maps to the NetSuite SO number (e.g., SO2)

5. Simulate shipment

  • Using the shipment ID from the create order response, simulate shipping:
1POST https://sandbox-api.shipbob.com/2025-07/simulate/shipment
2Authorization: Bearer <YOUR_API_TOKEN>
3shipbob_channel_id: 168384
4
5{
6 "shipment_id": <INSERT_SHIPMENT_ID>,
7 "simulation": {
8 "action": "ShipOrder",
9 "delay": null
10 }
11}

6. Test tracking sync

  • GET /2026-01/order?HasTracking=true&IsTrackingUploaded=false
  • Verify the test order appears in results with a tracking number
  • Confirm a NetSuite Item Fulfillment record is created
  • POST /2026-01/shipment:batchUpdateTrackingUpload to mark as synced
  • Re-run poll and confirm the order no longer appears

7. Test duplicate order prevention

  • Submit the same order reference_id a second time
  • Confirm a 422 error is returned and handled gracefully

8. Test order cancellation

  • Create an order, then cancel it before it is picked
  • Verify cancellation via POST /order/{id}/cancel

Additional Resources


Questions? Contact Simon Gondeck at ShipBob — happy to jump on a call anytime.