Webhooks
In this section, we will explore all of the available webhooks, list some examples, and see how you can register and unregister webhooks.
A webhook is a way for an app to provide other applications with real-time information. A webhook delivers data to other applications as it happens, meaning you get data immediately.
In ShipHero we have several different webhooks you can subscribe to, and when doing so, you should be receiving different kind of information, for example, if you subscribe to the Inventory Update webhook, we will be sending the webhook each time your inventory gets updated.
When we send the webhook we wait for a response:
HEADERS: Content-Type:application/json
BODY:
{
"code": "200",
"Status": "Success"
}The timeout for this response to get back is 10 seconds with 5 retries per trigger. For Generate Label Webhook the timeout is 20 seconds.
Note
Although it is not often, it is possible for a webhook not to fire, and that is why your app should not rely just on receiving data from our webhooks.
The webhook delivery is not always guaranteed, so we strongly suggest implementing reconciliation jobs to periodically fetch data from ShipHero.
Our most important Queries have both the created_at_min and updated_at_min filter parameters, which will allow you to build a job that fetches all resources that have been created or updated since the last time the job ran.
To be able to register a new webhook you will need to use the webhook_create mutation.
If you want to update an existing webhook, you will need to delete the existing one and then add it again.
Webhooks available are:
- Inventory Update
- Inventory Change
- Shipment Update
- Automation Rules
- Order Canceled
- Capture Payment
- PO Update
- Return Update
- Tote Complete
- Tote Cleared
- Order Packed Out
- Package Added
- Print Barcode
- Order Allocated
- Order Deallocated
- Shipment ASN
- Generate Label Webhook
Register a Webhook
The mutation to register a webhook could be:
mutation {
webhook_create(
data: {
name: "Inventory Update"
url: "https:someValidURL.net/"
shop_name: "api"
}
) {
request_id
complexity
webhook {
shared_signature_secret
}
}
}The field name is the webhook type you want to register to, for example, Inventory Update. Use the name as written in the list above, respecting spaces and capitals.
The field shop_name is an identifier that doesn’t necessarily need to be the shop’s name. If you have more than one endpoint you need the same webhook sent to, using a different Shop Name will allow you to register a second webhook of the same type.
The response for it should be something like this:
{
"data": {
"webhook_create": {
"request_id": "60dd12525e4d76b3ee093d7d",
"complexity": 5,
"webhook": {
"shared_signature_secret": "b0b588622fb191c3d96c34c5f929fbae04b94e74"
}
}
}
}The shared_signature_secret that you will get in return is the one you will be used to validate the webhook using the HTTP_X_SHIPHERO_HMAC_SHA256
shared_signature_secret will only display once, when first registering the webhook.
If you are using a 3PL account, webhooks need to be registered on the Customer account and not the 3PL account to trigger correctly.
Un-register an existing webhook
To be able to un-register an existing webhook you will need to use the webhook_delete mutation, for example:
mutation {
webhook_delete(data: {
name: "PO Update",
shop_name: "api" })
{
request_id
complexity
}
}List all registered webhooks
If you need to get a list of all the webhooks you have registered to your account, you will have to use the webhooks query:
query {
webhooks {
request_id
complexity
data {
edges {
node {
id
legacy_id
account_id
shop_name
name
url
source
}
}
}
}
}And the response for it should be something like this:
{
"data": {
"webhooks": {
"request_id": "5f04e1fcda845a0a76d4b102",
"complexity": 101,
"data": {
"edges": [
{
"node": {
"id": "QXBpV2ViaG9vazoxNjE0ODc4",
"legacy_id": 1614878,
"account_id": "QWNjb3VudDo2MzM0",
"shop_name": null,
"name": "Order Canceled",
"url": "https://someURL.x.webhook.net",
"source": "api"
}
},
{
"node": {
"id": "QXBpV2ViaG9vazoxNjE0Nzky",
"legacy_id": 1614792,
"account_id": "QWNjb3VudDo2MzM0",
"shop_name": null,
"name": "PO Update",
"url": "https://someURL.x.webhook.net",
"source": "api"
}
},
{
"node": {
"id": "QXBpV2ViaG9vazoxNjE1MDQy",
"legacy_id": 1615042,
"account_id": "QWNjb3VudDo2MzM0",
"shop_name": null,
"name": "Return Update",
"url": "https://someURL.x.webhook.net",
"source": "api"
}
}
]
}
}
}
}Request Verification
Webhooks data sent from ShipHero will need to be verified with the secret found within the API section of the Settings area.
Each Webhook request includes a x-shiphero-hmac-sha256 header which is generated using the app’s shared secret, along with the data sent in the request.
Note
The Generate Label Webhook uses the “default” API Secret unless the order’s shop name matches an entry in the API Secrets list. For 3PL child relationships, the system always uses the 3PL’s secret, even when use_customer_shipping_account is enabled.
If a child customer has a direct relationship with the API Carrier and 3PL secret protection is required, shop-specific secrets can be created for each child customer. This allows sharing only the relevant credentials while keeping the default 3PL secret private.
You can retrieve all your API secrets from this link. If the default secret is missing or has been replaced, you can generate a new one and name it accordingly.
To verify that the request came from ShipHero, compute the HMAC digest according to the following algorithm and compare it to the value in the x-shiphero-hmac-sha256 header.
If they match, you can be sure that the Webhook was sent from ShipHero and the data has not been compromised.
Note that if you are using a Rack-based framework such as Ruby on Rails or Sinatra the header you are looking for is HTTP_X_SHIPHERO_HMAC_SHA256
Below is a simple example in Python, showing how one might verify a webhook request.
import hmac
import hashlib
import base64
from typing import Union
def verify_webhook_hmac(
secret: Union[str, bytes],
data: Union[str, bytes],
hmac_signature: Union[str, bytes],
) -> bool:
"""
Verifies the authenticity of a webhook request by comparing the incoming
signature with a locally generated one.
Args:
secret: The shared secret key used for signing
data: The request payload to verify
hmac_signature: The signature received in the request header
Returns:
bool: True if the signature is valid, False otherwise
"""
# Convert secret to bytes if it's a string
if isinstance(secret, str):
secret = secret.encode('utf-8')
# Convert data to bytes if it's a string
if isinstance(data, str):
data = data.encode('utf-8')
# Generate the HMAC signature
digest = hmac.new(secret, msg=data, digestmod=hashlib.sha256).digest()
calculated_signature = base64.b64encode(digest)
# Use constant-time comparison to prevent timing attacks
return hmac.compare_digest(calculated_signature, hmac_signature)