Table of Contents
External Shipping Method API
Updated by Colin
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.
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.create_return
- Generate and return a new Return shipping labelcancel_return
- Cancel a previously generated Return shipping labelfetch_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,
"reason_for_export": "sold",
"export_tax_identifier": {
"type": "ioss_id",
"issuing_country": "IE",
"number": "IM3720000960"
},
"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",
"tracking_url": "<optional url>",
"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",
"tracking_url": "<optional url>",
"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"
},
]
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
Return all tracking details for the given tracking_number
. The tracking number has an overall Tracker status
and each Event must also have a status
which are one of the statuses in the table below. A "Final" event status indicates that no further attempts will need to be made to update the tracking info.
Status | Tracker | Event | Final | Notes |
pre_transit | ✅ | ✅ | ||
in_transit | ✅ | ✅ | ||
available_for_pickup | ✅ | ✅ | (for customer to pick up at a carrier location) | |
out_for_delivery | ✅ | ✅ | ||
delayed | ✅ | |||
delivery_attempted | ✅ | |||
delivered | ✅ | ✅ | ✅ | |
return_to_sender | ✅ | ✅ | ✅ | |
cancelled | ✅ | ✅ | ✅ | |
undelivered | ✅ | ✅ | ✅ | |
failure | ✅ | ✅ | (any other errors not listed above) |
The estimated_delivery_date
and estimated_delivery_time
are optional and should be mutually exclusive.
Request - sample_fetch_tracking_request.json
{
"action": "fetch_tracking",
"tracking_number": "552hhj2j2kj3yuh7q"
}
Response - sample_fetch_tracking_response.json
{
"tracking_number": "552hhj2j2kj3yuh7q",
"signed_by": "J. Smith",
"estimated_delivery_date": "2019-01-23",
"estimated_delivery_time": "2019-01-23T15:00:00-05:00",
"status": "delivered",
"tracking_events": [
{
"status": "pre_transit",
"event": "Collected from pickup",
"timestamp": "2019-01-22T12:54:34-05:00",
"location": {
"city": "New York City",
"region": "NY",
"postcode": "13088",
"country": "US"
}
},{
"status": "in_transit",
"event": "Scanned into sort facility",
"timestamp": "2019-01-22T15:14:44-05:00",
"location": {
"city": "New York City",
"region": "NY",
"postcode": "13088",
"country": "US"
}
}
]
}