Webhooks Guide

Introduction

The Webhooks API allows your application to stay informed about events of interest on the Shipwire platform, in near real-time. By creating a webhook, your application can subscribe to specific topics and tell us where to send notifications for each. Please note that webhooks, just like our API, contain a maximum of 20 items per resource. If there are more than 20 items for a specific resource that you need (most commonly items, trackings, pieces, or serialNumbers on an orders webhook topic), then you can use the Shipwire API URL given in “next” to retrieve the next page of additional items until you’ve retrieved them all. If there are only 20 or less items, then “next” will be null, and that is the easiest way to know that you have acquired all the items for a given resource.

Requirements for webhook endpoints

Webhook recipients are expected to conform to the following:
  • URLs must be for HTTPS hosts with valid and verifiable SSL certificates
  • Servers must reply to POST requests with a 200-level response within 5 seconds. (Refer to Guarantees and caveats in case of failures)
  • Servers must reply to HEAD requests with a 200-level response within 5 seconds (HEAD requests are occasionally used to validate the endpoint, e.g. at subscription time)
  • Servers must reply to GET requests with a 200-level response within 5 seconds
Here is the list of topics you can subscribe to and get information on when the associated event occurs.

Order topics

Topic Triggered When Payload Resource
order.canceled Sent when an order is canceled /api/v3/orders/:id
order.created Sent when an order is created /api/v3/orders/:id
order.updated Sent when an order is updated. Note: order routing is not considered an update. Also note that this generates a lot of traffic, and most likely you would be better served by using a more specific webhook, depending on your need. /api/v3/orders/:id
order.processed Sent when an order is cleared of all holds and ready for submission to the warehouse /api/v3/orders/:id
order.submitted Sent when an order is submitted to the warehouse /api/v3/orders/:id
order.completed Sent when an order has been ship confirmed by the warehouse /api/v3/orders/:id
order.completed.with-po-sku-source Sent when an order associated with a purchase order has been ship confirmed. Contains product information from purchase order for up to 20 items. If an order has more than 20 items, please retrieve the additional items from the orders API using the purchaseOrderItems subresource like: https://api.shipwire.com/api/v3/orders/{id}/purchaseOrderItems?offset=20&limit=20 /api/v3/orders/:id
order.hold.added Sent when a hold is added to an order /api/v3/orders/:id/holds
order.hold.cleared Sent when a hold is cleared for an order /api/v3/orders/:id/holds
tracking.created Sent when tracking information is first received for an order /api/v3/orders/:id/trackings
tracking.delivered Sent when tracking information indicates the order has been delivered /api/v3/orders/:id/trackings
tracking.updated Sent when tracking information has been updated for an order /api/v3/orders/:id/trackings
 

Purchase Order topics

Topic Triggered When Payload Resource
purchaseorder.created Sent when a purchase order is created /api/v3/purchaseOrders/:id
purchaseorder.updated Sent when a purchase order is updated /api/v3/purchaseOrders/:id
purchaseorder.needs-approval Sent when a purchase order enters a state where it needs approval /api/v3/purchaseOrders/:id
purchaseorder.approved Sent when a purchase order is approved /api/v3/purchaseOrders/:id
purchaseorder.posted Sent when all the related fulfillment orders have finished being created /api/v3/purchaseOrders/:id
purchaseorder.partially-completed Sent when a purchase order is partially completed (when one of the related fulfillment order is completed) /api/v3/purchaseOrders/:id
purchaseorder.completed Sent when a purchase order is completed /api/v3/purchaseOrders/:id
purchaseorder.canceled Sent when a purchase order is canceled /api/v3/purchaseOrders/:id
purchaseorder.hold.added Sent when a hold is added to an purchase order /api/v3/purchaseOrders/:id/hold
purchaseorder.hold.cleared Sent when a hold is cleared for an purchase order /api/v3/purchaseOrders/:id/hold
 

Stock topics

Topic Triggered When Payload Resource
stock.transition Sent when product stock moves from one state to another (e.g. an order moves 3 “in-stock” units to “reserved”). Please note that this generates *a lot* of activity as items move through different states as orders are processed – most merchants just need stock.transition.good see below
stock.transition.good Sent when “good” product stock changes (e.g. when an order is placed or new inventory is received at a warehouse). They are a subset of stock.transition where either “toState” or “fromState” is good. see below
stock.backordered Sent when product is backordered (e.g. When order is placed for a product that has no inventory) see below
alert.low-stock Sent when a configured low-stock alert is triggered see below
alert Sent when a generic alert is triggered see below
 

Return topics

Topic Triggered When Payload Resource
return.created Sent when a return is created /api/v3/returns/:id
return.updated Sent when a return is updated /api/v3/returns/:id
return.canceled Sent when a return is canceled /api/v3/returns/:id
return.completed Sent when a return is completed /api/v3.1/returns/:id
return.hold.added Sent when a hold is added to a return /api/v3/returns/:id/holds
return.hold.cleared Sent when a hold is cleared for a return /api/v3/returns/:id/holds
return.tracking.created Sent when tracking information is first received for a return /api/v3/returns/:id/trackings
return.tracking.updated Sent when tracking information has been updated for a return /api/v3/returns/:id/trackings
return.tracking.delivered Sent when tracking information indicates a return has been received by the warehouse /api/v3/returns/:id/trackings
 

Receiving (ASN) topics

Topic Triggered When Payload Resource
receiving.created Sent when a receiving order is created /api/v3/receivings/:id
receiving.updated Sent when a receiving order is updated /api/v3/receivings/:id
receiving.canceled Sent when a receiving order is canceled /api/v3/receivings/:id
receiving.completed Sent when a receiving order is completed /api/v3.1/receivings/:id
receiving.hold.added Sent when a hold is added to a receiving order /api/v3/receivings/:id/holds
receiving.hold.cleared Sent when a hold is cleared for a receiving order /api/v3/receivings/:id/holds
receiving.tracking.created Sent when tracking information is first received for a receiving order /api/v3/receivings/:id/trackings
receiving.tracking.updated Sent when tracking information has been updated for a receiving order /api/v3/receivings/:id/trackings
receiving.tracking.delivered Sent when tracking information indicates a receiving order has been received by the warehouse /api/v3/receivings/:id/trackings
receiving.shipment.received Sent when a shipment (partial or full) is received as part of a receiving order. For partial shipment it will be sent for each shipment /api/v3/receivings/:id
 

Product topics

Topic Triggered When Payload Resource
product.created Sent when a product order is created /api/v3/product/:id
product.updated Sent when a product is updated /api/v3/product/:id
product.retired Sent when a product is retired /api/v3/product/:id

POST bodies

Webhooks are POSTed to endpoints with a common envelope wrapping a topic-dependent resource. For most topics, the resource, or “body”, is identical to the resource you would get from a related API endpoint. For example, on a tracking.updated webhook, you might get:
{ "attempt": 1, "body": { "status": 200, "message": "Successful", "resourceLocation": "https://api.shipwire.com/trackings/638903765", "resource": { "id": 638903765, "orderId": 186903450, "orderExternalId": null, "tracking": "294631149443923572", "carrier": null, "url": null, "summary": "Shipment information sent to FedEx", "summaryDate": "2015-05-05 03:17:10", "trackedDate": "2015-05-05 03:17:10", "deliveredDate": null } }, "timestamp": "2015-05-12T11:01:51-07:00", "topic": "tracking.updated", "uniqueEventID": "ba898330ad9b9dfd41de247c09bf7b96", "webhookSubscriptionID": 222 }
The object above, rooted at “body”, is the same object that you would get from the order-tracking API. The exceptions to this pattern are “stock.transition”, “alert”, and “alert.low-stock”, which contain data unique to those webhook topics, and are described below:

“stock.transition.good” webhook body, showing a shipping order consuming a quantity of 1 good of SKU ABC123 once it has been routed to warehouse 12

{ "attempt": 1, "body": { "productId": 515783606, "productSku": "ABC123", "warehouseId": 12, "fromState": "good", "toState": "reserved", "delta": 1, "toStateStockAfterTransition": 3, "fromStateStockAfterTransition": 2993, "type": "shipment", "orderId": 622263970, "physicalWarehouseId": null }, "timestamp": "2021-10-21T08:09:11-07:00", "topic": "stock.transition.good", "uniqueEventID": "0fbb2dd3d8f23000", "webhookSubscriptionID": 123456 }   It is important to note that the toStateStockAfterTransition and fromStateStockAfterTransition fields are meant to be references, and may not necessarily contain the most up-to-date stock values. This is because webhooks may get sent in a different order than how the events occurred. Possible stock transition states are “pending”, “good”, “reserved”, “backordered”, “shipping”, “shipped”, “created”, “damaged”, “returned”, “inreview”. Possible values for inventory transition type are “shipment”, “return”, “receiving”, “adjustment”, “unknown”. For correct and up-to-date information, use the Stock endpoint.   Note that the Adjustment Reason Codes (“reason” and “reasonCode”) will only be populated when there has been an adjustment to inventory, typically within an inventory state, within the warehouse or manual disposition by operations/engineering support. Adjustments occur in the following inventory buckets: “good”, “damaged”, “quarantined”, “in review”, and “consuming” and are not part of the inbound receiving and outbound shipping processes.  

“stock.transition.good” webhook body for an inventory adjustment, moving 6 units from good to damaged

{ "attempt": 1, "body": { "productId": 6349911, "productSku": "ABC123", "warehouseId": 12, "fromState": "good", "toState": "damaged", "delta": 6, "toStateStockAfterTransition": 144, "fromStateStockAfterTransition": 21926, "type": "adjustment", "reason": "None", "reasonCode": "NONE", "lotId": "123", "lotQuantity": 6, "lotExpirationDate": "20230329", "physicalWarehouseId": null }, "timestamp": "2021-10-21T07:31:03-07:00", "topic": "stock.transition.good", "uniqueEventID": "0fbb258126723000", "webhookSubscriptionID": 123456 }

“alert” and “alert.low-stock” webhook body

{ "id": "2", "type": "backordered", "name": "A backordered alert", "triggeredFor": { "warehouseId": 1038, "productId": 528 }, "triggerCondition": { "type": "backordered", "value": 1104, "threshold": 1 } } { "id": "123", "type": "low_stock", "name": "Inventory Alert", "triggeredFor": { "warehouseId": 1, "productId": 12345 }, "triggerCondition": { "type": "quantity", "value": 99, "threshold": 100 } }

Deciphering Reasons and Reason Codes in stock.transitions webhooks

reasonCode reason Examples
RER Receiving Error Operator error at receiving, ASN Carton Shortage/Overage
SPK Special Projects/Kitting Value Added Services/Special Projects
CYC Cycle Count Ad hoc or scheduled cycle count
LIQ Liquidation, customer initiated Customer-requested destruction of material
RET Returns/Refusals order voided per customers request/refusal after it has already been picked and billed
SER Shipping Error Corrections due to short/over/cross shipment
SYS System Error Data problem found
QHA Increase or Movement into Quality Hold Potential quality issue with material
QHR Decrease or Movement out of Quality Hold Potential quality issue with material addressed
CHA Increase or Movement into C-Hold Potential loss of material
CHR Decrease or Movement out of C-Hold Potentially lost material has been found
OTH Other Other, rare adjustment reasons
NONE No reason code given by warehouse No reason code given by warehouse

Webhook versions

Currently, the Webhooks API has only the “v1” version. In the future, additional Webhook API versions will be added as needed to support new capabilities and topics to subscribe to. If you want your integration with Shipwire to be tied to a specific version of the Webhook API which has a particular payload structure and content version, you can specify the API version as a prefix to the topic.   - "v1.order.created" - "v1.order.updated" - "v1.order.canceled" - "v1.order.completed" - "v1.order.hold.added" - "v1.order.hold.cleared" - "v1.tracking.created" - "v1.tracking.updated" - "v1.tracking.delivered" - "v1.stock.transition" - "v1.stock.transition.good" - "v1.alert.low-stock" - "v1.alert"
Subscribing to topics without a prefixed version number means that your integration will use the latest API payload version at the time of subscription. For example, if you register now for the topic order.created, your subscription will be tied to v1. After v2 is made available, your existing subscriptions will remain with v1 and that distinction will not change. However, if you now add new subscriptions without specifying the API version, those new subscriptions will be tied to v2. By specifying a version, you can ensure that each of your topic subscriptions are associated with the version of the Webhook API that you want.

Guarantees and caveats

Webhooks are currently delivered on a best-effort basis. Shipwire will typically deliver a webhook message within minutes of an event occurring and usually much sooner. The message will be sent once and only once while Shipwire and your application’s servers both remain connected and available. When a failure occurs on a POST request to the consumer, the message will be retried. Between each subsequent attempt for the same webhook, we wait progressively longer. We make a total of 10 attempts, where the final attempt occurs at least 5 and a half hours after the initial attempt. If your server consistently fails to respond to webhook messages, we may, at our discretion, unregister the webhook topics that are failing. Once you’ve resolved any issues, you may then re-register for the webhook topics. Please do not implement IP address whitelisting on your end – we send webhooks from a number of different IP addresses, and this list regularly changes. If webhook authentication is required, use Secrets.

Secrets

So that applications can validate the authenticity of events, Shipwire can optionally sign events with a shared secret. Secrets are managed through the Secrets resource. Secret keys are 64 bytes, encoded as 128-digit hexadecimal strings. You may have multiple shared secrets active at a given time, in which case Shipwire will sign events with all active secrets (see example below).

Verification

Webhooks should be verified by the consumer to confirm that information is authentic. Accounts which have at least one active API Secret will have webhook events with a signature for each valid secret as an HTTP header. Example:   X-Shipwire-Signature: abc123;secret-id=2 X-Shipwire-Signature: bcd345;secret-id=5   The hash value is the HMAC-SHA256 of the unaltered POST request body. Having multiple secrets allows you to rotate your keys without missing messages.

Signature verification code samples

Here is a sample of how you could verify your webhook secrets in PHP   <?php $key = hex2bin(" (your key here) "); if ($key === FALSE) { echo "invalid key"; return; } $request = file_get_contents("php://input"); if (strlen($request) == 0) { $request = file_get_contents("php://stdin"); } echo strlen($request) . " bytes. "; echo hash_hmac("sha256", $request, $key);   Here is a sample of how you could verify your webhook secrets in Go   package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "flag" "fmt" "io" "log" "os" ) func main() { flag.Parse() if flag.NArg() < 1 { log.Fatal("need key") } // decode the key from hex to binary ([]byte) key, err := hex.DecodeString(flag.Arg(0)) if err != nil { log.Fatal(err) } // compute hash h := hmac.New(sha256.New, key) l, err := io.Copy(h, os.Stdin) if err != nil { log.Fatal(err) } fmt.Printf("%d bytes: %x\n", l, h.Sum(nil)) }

API reference

View the webhooks API reference here