> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.shipbob.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.shipbob.com/_mcp/server.

# Integration Best Practices

## Authentication & Credentials

**Use the right header format for the right endpoint.** Both endpoint groups use a Personal Access Token (PAT) generated from **Integrations > API Tokens** in the dashboard, but the header format differs. The `logisticslabelprintingapi` endpoints (label creation, bulk cancel, rate shopping) use `Authorization: <PAT>`. The `api.shipbob.com` / `sandbox-api.shipbob.com` endpoints (webhook subscriptions) use `Authorization: Bearer <PAT>` — this is the more common pattern. Mixing up the header format is the most common cause of `401` errors.

**Rotate and store credentials securely.** Never hardcode tokens in client-side code or commit them to source control. Store them in environment variables or a secrets manager.

***

## Testing in Sandbox First

**Always validate your integration in sandbox before going to production.** The sandbox environment (`logisticslabelprintingapi-stage.shipbob.dev`) behaves identically to production for label creation, rate shopping, and webhooks. Use it to test edge cases like oversized packages, signature-required orders, and cancellation/resubmission flows without incurring real shipping costs.

**Use `location_id: 33` (or `19`) in sandbox.** These are the fixed sandbox location IDs for label creation and rate shopping. Using any other value will cause failures. Your production `location_id` is different — get it from your ShipBob rep before going live.

**Add the test credit card during sandbox setup.** Sandbox label creation requires a payment method on file. Use card number `4242 4242 4242 4242` with any valid CVC, expiration date, and zip code.

***

## Label Creation

**Use whole numbers for all package measurements.** The `measurements` object requires integer values for weight (in ounces) and all dimensions (in inches). Passing decimals may result in unexpected behavior.

**Make `reference_id` truly unique per order.** This is your primary identifier for an order within ShipBob Logistics. Reusing a `reference_id` is only valid after explicitly canceling the original via the bulk-cancel endpoint. Otherwise, the API may reject or conflict the submission.

**Know when `requires_signature` is automatically enforced.** If you set `insurance_amount` to \$300 or more, signature confirmation is applied automatically regardless of your `requires_signature` field value. Account for this in your downstream order processing logic.

**Pass `products` when you want pick lists and batching.** The `products` array is optional, but including it unlocks pick list printing and order batching features in the ShipBob dashboard.

***

## Rate Shopping

**All fields are mandatory.** Unlike the label creation endpoint (which has several optional fields), the rate shopping request requires every field — `actualWeight`, all three `packageDimensions`, `destinationAddress`, `fulfillmentCenterId`, `serviceName`, `hasSignature`, and `insuranceAmount`. Missing any field will cause the request to fail.

**Match `serviceName` exactly to your ShipBob dashboard ship option.** The value is case-sensitive and must match your configured ship option name precisely (e.g., `"Standard Ground"`, not `"standard ground"` or `"Standard Ground "`). Mismatches are a common source of no-results responses.

**Use rate shopping before label creation for cost-sensitive workflows.** Call the rate shopping endpoint first to retrieve the `flatRate` for a given service and destination, then decide whether to proceed with label creation. This is especially useful if you're offering customers real-time shipping cost estimates.

***

## Tracking Updates

**Prefer webhooks over polling.** The `order.shipment.tracking_received` webhook typically fires within 5 minutes of label creation. Webhooks are more reliable and timely than polling for high-volume integrations. Reserve polling for fallback or reconciliation scenarios.

**If you do poll, don't exceed every 30 minutes.** Polling more frequently than that offers little benefit and adds unnecessary load. Tracking details are generated within 3 minutes of the label being created, so it is recommended to poll at least every 3 minutes when retrieving tracking details.

**Your webhook handler must return HTTP 200.** ShipBob considers any non-2xx response a delivery failure. Ensure your handler responds with `200 OK` before performing any heavy downstream processing — use an async queue if needed.

**Verify your webhook URL is publicly accessible.** Local development URLs (e.g., `localhost`) will not receive events. Use a tunneling tool like ngrok during development and register the public URL when subscribing.

***

## Carrier & Service Mapping

**API carrier values are case-sensitive and must be exact.** When specifying carrier services, use the values from the [Carriers reference table](https://developer.shipbob.com/logistics/carriers) exactly as listed. For example, DHL's API carrier value is `DhlEcs`, not `DHL` or `dhlecs`.

**Be aware of service-level aliases.** Several DHL services (e.g., "Parcel Plus Expedited Domestic" and "Parcels Expedited Domestic") map to the same API Carrier Service Value (`DHLParcelExpedited`). If you're building a carrier-mapping layer, account for these many-to-one relationships to avoid duplicating logic.

***

## Error Handling

**Handle `400` and `500` errors differently.** A `400` indicates a problem with your request payload — validate your JSON structure and required fields before retrying. A `500` is a server-side issue; implement exponential backoff and retry logic rather than failing immediately.

**Log `interim_order_id` and `shipment_id` from label creation responses.** These IDs are your references for tracking down issues with ShipBob support. Store them alongside your internal order record.