External Shipping Method API

Updated 6 months ago by Colin Mollenhour

This article is a guide for implementing an API endpoint for an External Shipping Method which has the Label URL specified. By implementing this API endpoint the ShipStream users will be able to fetch labels on demand and the information returned will be stored in ShipStream just like it would for one of the officially supported carriers. All related functionality such as automatic printing and webhook events will function the same as well. This allows you or your third-party service providers to create your own custom carrier integrations while still benefiting from all of the features of ShipStream.

Getting Started

To begin, you will need to determine what the service codes will be and what the endpoint URL will be. Create and configure the External Shipping Methods as needed as described here.

HTTP Requests

When ShipStream needs to create or cancel a label it will POST an HTTP request to the specified Label URL. When it needs tracking information it will POST an HTTP request to the specified Tracking URL. In all cases the request body will be a JSON encoded object and a JSON encoded object is the expected response.

Security

It is recommended to secure your endpoint URLs with HTTPS using TLS 1.2 and strong ciphers. While this will ensure that the data is kept private in-transit it does not prevent spoofed requests since there is no authentication required for the endpoint URLs. However, for increased security if the HMAC Secret Key is specified in the ShipStream configuration some additional headers will be included in the request which your endpoint may verify. These are a signature for the request which if verified can prove that the request is legitimate given that the HMAC Secret Key has not been compromised.

It is not recommended to use a firewall to block access to your endpoint by IP address because the IP address is not guaranteed to remain static.

HMAC stands for "hash-based message authentication code". In this case, the HMAC signature is a cryptographic signature of the request body using the HMAC Secret Key that is known to both ShipStream and the endpoint URL. This signature allows the endpoint to attempt to generate the same hash from the request data and if the hashes match, the request can safely be assumed to be both originating from the trusted source and also not modified en-route.

The HTTP request from ShipStream will look like this:

POST /<your-endpoint-uri>
Content-Type: application/json
X-ShipStream-Salt: QLdIigZpb2u97O306hkIJl00coS9RyMS
Authorization: af013e81a86e4b61587bfaf6887c7b2868411551

{"action":"create_label","<key1>":"<value1>"}

The above request was generated using the following secret key: blap47MMJ5yKwo8qOkVWz2nAB7AUYHP2

To verify the request, your endpoint needs to concatenate the X-ShipStream-Salt header value and the request body and then generate an HMAC hash using the SHA1 hashing function and the secret key.

In PHP this would be done like so (simplified code not including error checking):

<?php
$salt = $_SERVER['HTTP_X_SHIPSTREAM_SALT'];
$secret = 'blap47MMJ5yKwo8qOkVWz2nAB7AUYHP2';
$body = file_get_contents('php://input');
$hash = hash_hmac('sha1', $salt.$body, $secret, FALSE);
if ($hash === FALSE || $_SERVER['HTTP_AUTHORIZATION'] !== $hash) {
die('Invalid request.');
}
$request = json_decode($body);
switch($request->action) {
// ...
}

Actions

Each request will include a key named "action" which specified what type of action is expected to be taken. The actions are:

  • create_label - Generate and return a new shipping label.
  • cancel_label - Cancel a previously generated shipping label.
  • fetch_tracking - Provide tracking information for a previously generated tracking number.

The following sections will show example requests and responses for these three actions.

Create Label

Request - sample_create_label_request.json
{
"action": "create_label",
"stock_id": "1",
"order_increment_id": "1100000029",
"shipment_id": "1100000062",
"shipment_increment_id": "1100000062",
"shipment_total_qty": "14.3333",
"ext_order_id": null,
"order_id": "42",
"tpb_group_id": null,
"other_shipping_options": "",
"address_classification": "res",
"carrier_code": "external",
"service": "test_service_123",
"total_weight": "23.2670",
"signature_required": "none",
"saturday_delivery": "0",
"declared_value_service": "0",
"store_code": "acme_inc",
"reference_data": "Order Ref # 1100000029",
"short_reference_data": "1100000029",
"package_id": null,
"order_unique_id": "1100000029",
"order_ref": null,
"shipper_address": {
"name": "Sherlock Marquez",
"firstname": "Sherlock",
"lastname": "Marquez",
"company": "ACME Inc.",
"telephone": "+1999999999",
"email": "AcmeInc@example.com",
"street1": "4616 Crossroads Park Dr",
"street2": "",
"city": "New York City",
"region_name": "New York",
"region_code": "NY",
"postcode": "13088",
"country": "US"
},
"printed_label_address": {
"name": "Sherlock Marquez",
"firstname": "Sherlock",
"lastname": "Marquez",
"company": "ACME Inc.",
"telephone": "+1 999-999-999",
"email": "AcmeInc@example.com",
"street1": "4616 Crossroads Park Dr",
"street2": "",
"city": "New York City",
"region_name": "New York",
"region_code": "NY",
"postcode": "13088",
"country": "US"
},
"recipient_address": {
"country": "US",
"name": "Celina Goalley",
"firstname": "Celina",
"lastname": "Goalley",
"company": "Thompson-Torp",
"telephone": "4175193773",
"street1": "15491 West Whiteside Street",
"street2": "",
"city": "Springfield",
"region_name": "Missouri",
"region_code": "MO",
"region_id": "36",
"postcode": "65807",
"classification": "res"
},
"package": {
"unique_id": "shipment_76-nYkq",
"stock_id": "1",
"weight": "23.1",
"customs_value": "0.000",
"length": "",
"width": "",
"height": "",
"weight_units": "POUND",
"dimension_units": "INCH",
"created_at": null,
"container": "unsupported",
"shipment_number": null,
"shipping_method": "external_test_service_123",
"regulation_type": "normal",
"items": [
{
"product_id": "75",
"qty": "4",
"weight": "0.6000",
"name": "O'hare Air - Single",
"ext_order_item_id": null,
"order_item_sku": "AIR-4",
"order_item_qty": "1.0000",
"sku": "AIR-1",
"goods_type": "NORMAL",
"country_of_manufacture": null,
"export_description": null,
"hts_base_code": null,
"customs_value": "0"
},
{
"product_id": "77",
"qty": "1",
"weight": "7.7000",
"name": "O'hare Air - Case of 12",
"ext_order_item_id": null,
"order_item_sku": "AIR-12",
"order_item_qty": "1",
"sku": "AIR-12",
"goods_type": "NORMAL",
"country_of_manufacture": null,
"export_description": null,
"hts_base_code": null,
"customs_value": "0"
},
{
"product_id": "82",
"qty": "2",
"weight": "1.0000",
"name": "Fidget Widget Blue Part",
"ext_order_item_id": null,
"order_item_sku": "FWB",
"order_item_qty": "0.3333",
"sku": "FW-BP",
"goods_type": "NORMAL",
"country_of_manufacture": null,
"export_description": null,
"hts_base_code": null,
"customs_value": "0"
}
]
}
}
Response - sample_create_label_response.json

Only the "tracking_number" field is required, all other fields may be specified as needed.

[
{
"tracking_number": "552hhj2j2kj3yuh7q",
"tracking_description": "R&L PRO Number",
"alt_tracking_number": "1a5422nbss24",
"alt_tracking_description": "BOL Number",
"label_content": "<base64 encoded PNG>",
"auxiliary_label_content": "<base64 encoded PNG>",
"shipment_number": "2409",
"customs_sender_copy_content": "<base64 encoded PDF>",
"electronic_customs_documents": [
{"type": "<document type>", "title": "<recipient title>", "id": "<document id>"}
],
"hazmat_shipping_papers": "<base64 encoded PDF>",
"hazmat_shippers_declaration": "<base64 encoded PDF>",
"shipping_cost": "24.09"
},
{
"tracking_number": "112ksd1k1nn3nii1k",
"tracking_description": "R&L PRO Number",
"alt_tracking_number": "6a2424n5sa23",
"alt_tracking_description": "BOL Number",
"label_content": ["<base64 encoded PNG>", "..."],
"auxiliary_label_content": ["<base64 encoded PNG>", "..."],
"shipment_number": "2409",
"customs_sender_copy_content": "<base64 encoded PDF>",
"electronic_customs_documents": [
{"type": "<document type>", "title": "<recipient title>", "id": "<document id>"}
],
"hazmat_shipping_papers": "<base64 encoded PDF>",
"hazmat_shippers_declaration": "<base64 encoded PDF>",
"shipping_cost": "24.09"
},
]
The label content and auxiliary label content are expected to be PNG images between 200dpi and 300dpi and roughly 4"x6" aspect ratio. You may provide one string if there is only one label, or an array of strings if there are multiple labels.
Error Response
{
"errors": "This is an error message"
}
Shipment Number

The shipment_number can be used to join multiple requests. If "Combine Requests" in the ShipStream config is enabled you shouldn't need to use this since all packages are included in a single request, but if it wasn't, the shipment_number returned by the first shipment would be used in the request of the subsequent shipments so they could be linked (multi-piece shipments). This field can also be used as an alternative identifier to the tracking number when voiding a label in case the tracking number alone is not sufficient. The shipment_number is strictly internal and is not user-facing.

Cancel Label

Request - sample_cancel_label_request.json
{
"action": "cancel_label",
"shipment_id": "1100000062",
"order_id": "42",
"order_unique_id": "1100000029",
"order_ref": "EXT REF",
"store_code": "acme_inc",
"shipping_method": "external_test_service_123",
"packages": [
{
"tracking_number": "552hhj2j2kj3yuh7q",
"shipment_number": "2409"
},
{
"tracking_number": "112ksd1k1nn3nii1k",
"shipment_number": "2411"
}
]
}
Response - sample_cancel_label_response.json
[
{
"tracking_number": "552hhj2j2kj3yuh7q",
"shipment_number": "2409"
},
{
"tracking_number": "112ksd1k1nn3nii1k",
"shipment_number": "2411"
}
]
Error Response
{
"errors": "This is an error message"
}

Fetch Tracking

Request - sample_fetch_tracking_request.json
{
"action": "fetch_tracking",
"tracking_number": "552hhj2j2kj3yuh7q",
"method_code": "test_service_123"
}
Response - sample_fetch_tracking_response.json
{
"tracking_number": "552hhj2j2kj3yuh7q",
"method_code": "test_service_123",
"signed_by": "J. Smith",
"delivery_date": "2019-01-22T12:54:34-05:00",
"shipped_date": "2019-01-22T00:00:00-05:00",
"status": "Delivered",
"tracking_events": [
{
"event": "Collected from warehouse",
"date_time": "2019-01-22T12:54:34-05:00",
"location": {
"city": "New York City",
"state": "NY",
"zip": "13088",
"country": "US"
}
}
,{
"event": "logged into New York City",
"date_time": "2019-01-22T12:54:34-05:00",
"location": {
"city": "New York City",
"state": "NY",
"zip": "13088",
"country": "US"
}
}
]
}


How did we do?