With ShipTime's API, you can streamline your shipping processes, automate workflows, and integrate ShipTime's features directly into your own platform.
ShipTime also supports webhooks, allowing your application to receive real-time shipment updates as they occur.
Before You Begin
Before integrating with ShipTime Webhooks, ensure you have:
- A ShipTime account with API access.
- A publicly accessible HTTPS endpoint.
- A secure location to store your webhook secret.
Don't have a ShipTime account? Sign up at app.shiptime.com
Need API access? Click here for details on obtaining API credentials
Contents
Overview
Rather than repeatedly polling the Tracking API to determine whether a shipment has moved, register a webhook once. Whenever a shipment's tracking status changes, ShipTime sends a signed HTTP POST request containing a JSON payload to your endpoint.
A webhook integration has two parts:
- Manage Webhook subscriptions — Use the Webhook Subscriptions API (
/rest/webhooks) to register and manage the HTTPS endpoints that receive webhook events. These endpoints use the same authentication as the rest of the ShipTime REST API. - Receive Webhook events — Implement your own HTTPS endpoint to receive webhook requests, verify each request's signature, and process the event payload.
Quickstart
Follow these three steps to create a working webhook integration.
1 · Register your endpoint
# Uses your ShipTime API bearer token
curl -X POST https://api.shiptime.com/rest/webhooks \
-H "Authorization: Bearer $SHIPTIME_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.yourapp.com/shiptime",
"eventType": ["shipment.status_changed"],
"description": "Shipment status change listener"
}'
The 201 Created response includes a webhook secret. Store this value securely. It is returned only once and is required to verify all future webhook requests.
2 · Receive and verify
Implement an HTTPS endpoint that:
- Accepts HTTP POST requests.
- Reads the raw request body.
- Verifies the
X-ShipTime-Signatureheader. - Returns an HTTP
200 OKresponse after successful verification.
Signature verification is covered in detail in the Verifying signatures section
3 · Create a Shipment
Create a shipment using the ShipTime API as usual. As carriers scan the shipment, ShipTime automatically delivers webhook events to your registered endpoint in real time.
Event types
A webhook subscription's eventType array determines which events your endpoint receives. Omit the eventType field (or provide an empty array) to receive all supported events.
| Event type | Description | Payload notes |
|---|---|---|
shipment.status_changed | Sent only when a shipment changes status (for example, PENDING → IN_TRANSIT or IN_TRANSIT → DELIVERED). | Includes the previous status, new status, and tracking number. |
shipment.tracking_updated | Sent for every carrier tracking scan, including updates that do not change the shipment status. | Includes all shipment.status_changed data, plus the complete tracking_events scan history. |
Subscribe to shipment.tracking_updated if you need complete scan-by-scan tracking visibility. Subscribe to shipment.status_changed if you only need shipment milestones. Because shipment.tracking_updated is a superset of shipment.status_changed, subscribing to both is generally unnecessary.
Shipment Status Values
Webhook payloads may contain the following shipment status values:
DEFERRED PENDING MANIFESTED IN_TRANSIT
DELIVERED REFUND_REQUESTED CANCELLED EXCEPTION
Subscriptions API
Use the Webhook Subscriptions API to create, retrieve, update, and delete webhook subscriptions.
All subscription management endpoints are available under:
/rest/webhooks
Authentication
All /rest/webhooks endpoints use the same authentication as the rest of the ShipTime REST API. Include a Bearer JWT in the Authorization header. Your token must include the RESTAPI role (all standard API credentials do).Authorization: Bearer <your-jwt>
Create a subscription
Creates a new webhook subscription
EndpointPOST /rest/webhooks
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
url | string | required | HTTPS endpoint that will receive webhook events. Maximum 512 characters. |
eventType | string[] | optional | Events to subscribe to. Omit or provide an empty array to receive all events. |
description | string | optional | Optional label used to help identify the subscription. |
Response
201 Created
{
"id": 41,
"url": "https://hooks.yourapp.com/shiptime",
"secret": "a3f8c2d1e9b7...d491",
"active": true,
"eventType": ["shipment.status_changed"],
"description": "Shipment status change listener",
"createdAt": 1748908800000,
"updatedAt": null
}
The secret is returned only once when the subscription is created. Store it securely, as it is required to verify incoming webhook requests and cannot be retrieved later.
Rotating the secret
Re-registering the same URL with another POST request issues a new secret and reactivates the subscription if it was previously disabled.
Use this method to rotate a compromised secret. To update subscription fields without generating a new secret, use the Update a Subscription endpoint instead.
List subscriptions
Returns all webhook subscriptions associated with your account, including both active and inactive subscriptions.
Endpoint
GET /rest/webhooks
Returns an array of subscription objects (both active and inactive). The secret is never included in list or get responses.
Response
200 OK
Returns an array of subscription objects.
Note
The
secretis never included in list or get responses.
[
{
"id": 41,
"url": "https://hooks.yourapp.com/shiptime",
"active": true,
"eventType": ["shipment.status_changed"],
"description": "Shipment status change listener",
"createdAt": 1748908800000,
"updatedAt": 1749012400000
}
]
Get a single subscription
Returns a single webhook subscription.
EndpointGET /rest/webhooks/{id}
Response
Returns the subscription object.
If the subscription is not found, the API returns:
404 Not Found
Update a subscription
Updates an existing webhook subscription without rotating the webhook secret.
Endpoint
PUT /rest/webhooks/{id}
| Field | Type | Required | Description |
|---|---|---|---|
url | string | required | The endpoint URL. |
eventType | string[] | optional | Replaces the existing event filter. |
description | string | optional | Updates the subscription label. |
active | boolean | optional | Set to true to re-activate a disabled subscription. Omit to leave the current value unchanged. |
Response
200 OK
Returns the updated subscription object.
Delete a subscription
Soft-deletes a webhook subscription.
EndpointDELETE /rest/webhooks/{id}
Response
204 No Content
The subscription is marked as inactive (active = false) and webhook deliveries stop immediately.
To reactivate the subscription, either:
- Update the subscription with
PUTand set"active": true, or - Re-register the same URL using
POST
Delivery stops immediately. You can bring it back later with PUT … {"active": true} or by re-registering the URL with POST.
Receiving events
Once a webhook subscription is configured, ShipTime delivers webhook events as HTTP POST requests to your registered endpoint.
Each request contains a JSON payload describing the event.
Payload format
Every webhook delivery contains a JSON event envelope.
shipment.status_changed
{
"id": "b3d9a1f2-4c8e-4a1b-9f3d-2e7a6b5c8d9e",
"type": "shipment.status_changed",
"shipment_id": "10001",
"occurred_at": "2026-04-29T14:02:00Z",
"data": {
"previous_status": "PENDING",
"new_status": "IN_TRANSIT",
"tracking_number": "1Z999AA10123456784"
}
}
Event Fields
| Field | Description |
|---|---|
id | Unique event ID. Use this value as your deduplication key, as the same event may be delivered more than once. |
type | The webhook event type. |
shipment_id | The ShipTime shipment ID. Use this to correlate the event with the shipment created through the ShipTime API. |
occurred_at | ISO-8601 UTC timestamp of when the event occured |
data.previous_status | Status before the transition (may be null for the first event). |
data.new_status | Shipment status after the transition |
data.tracking_number | Carrier tracking number. |
shipment.tracking_updated
For shipment.tracking_updated events,the data object also includes the carrier's tracking scan history in the tracking_events array.
"data": {
"previous_status": "IN_TRANSIT",
"new_status": "IN_TRANSIT",
"tracking_number": "1Z999AA10123456784",
"tracking_events": [
{ "status": "IN_TRANSIT", "description": "Out for delivery",
"location": "Toronto, ON", "occurred_at": "2026-04-29T13:30:00Z" },
{ "status": "IN_TRANSIT", "description": "Arrived at facility",
"location": "Toronto Hub, ON", "occurred_at": "2026-04-29T11:00:00Z" }
]
}
Proof of Delivery
When a shipment reaches the DELIVERED status and the carrier provides proof of delivery information, the data.proof_of_delivery object is included in the payload.
"proof_of_delivery": {
"location": "Front door",
"signed_by": "J. SMITH",
"occurred_at": "2026-04-29T14:02:00Z"
}
Delivery headers
Every webhook request includes the following HTTP headers:
| Header | Example | Purpose |
|---|---|---|
X-ShipTime-Signature | t=1748908800,v1=a3f8c2… | Timestamp and HMAC-SHA256 signature used to verify the request. |
X-ShipTime-Event-Id | b3d9a1f2-4c8e-… | Matches the eventid. Use as an idempotency key. |
X-ShipTime-Delivery-Attempt | 0 | Zero-based delivery attempt number. |
X-ShipTime-Event-Type | shipment.status_changed | Event-type hint. See the note below. |
User-Agent | ShipTime-Webhooks/1 | Identifies ShipTime as the sender. |
Note.
Determine the webhook event type from the payload's
typefield.The
X-ShipTime-Event-Typeheader currently always reportsshipment.status_changed, even forshipment.tracking_updatedevents. Treat the header as a hint only.
Verifying signatures
Because your endpoint is publicly accessible, you should verify that every webhook request was sent by ShipTime.
Each webhook request is signed using your subscription's secret and the HMAC-SHA256 algorithm.
Signature Format
The X-ShipTime-Signature header uses the following format:
t=<unix-seconds>,v1=<hex-hmac>
Where:
tis the Unix timestamp (seconds).v1is the HMAC-SHA256 signature.
Verification Process
To verify a webhook request:
- Parse the
tandv1values from theX-ShipTime-Signatureheader. - Build the signed message by concatenating:
t- a literal
. - the raw request body (do not re-serialize the JSON)
- Compute the HMAC-SHA256 digest using your webhook
secret. - Compare the computed digest with
v1using a constant-time comparison. - Reject the request if the timestamp differs from the current time by more than 300 seconds to protect against replay attacks.
Message Construction
message = t + "." + raw_request_body expected = hex(HMAC-SHA256(secret, message)) valid = constantTimeEquals(expected, v1)
Example: Node.js (Express)
const crypto = require('crypto'); // Capture the RAW body for verification app.use(express.raw({ type: 'application/json' })); app.post('/shiptime', (req, res) => { const sig = req.get('X-ShipTime-Signature') || ''; const { t, v1 } = Object.fromEntries( sig.split(',').map(p => p.split('=')) ); if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.sendStatus(400); const msg = t + '.' + req.body; const expected = crypto .createHmac('sha256', SECRET) .update(msg) .digest('hex'); const ok = crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(v1 || '') ); if (!ok) return res.sendStatus(401); const event = JSON.parse(req.body); // Handle event.type // Deduplicate using event.id res.sendStatus(200); });
Example: Python (Flask)
import hmac, hashlib, time from flask import request, abort def verify(): sig = request.headers.get('X-ShipTime-Signature', '') parts = dict( p.split('=', 1) for p in sig.split(',') if '=' in p ) t, v1 = parts.get('t'), parts.get('v1') if not t or abs(time.time() - int(t)) > 300: abort(400) raw = request.get_data() msg = t.encode() + b'.' + raw expected = hmac.new( SECRET.encode(), msg, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(expected, v1 or ''): abort(401) # In the route: # verify() # event = request.get_json() # handle event['type'] # dedupe on event['id']
Example: Java
Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(UTF_8), "HmacSHA256")); byte[] out = mac.doFinal((t + "." + rawBody).getBytes(UTF_8)); String expected = Hex.encodeHexString(out); boolean ok = MessageDigest.isEqual( expected.getBytes(UTF_8), v1.getBytes(UTF_8) );
Important
Always verify the signature before processing the webhook payload. Reject requests with invalid signatures or stale timestamps before performing any business logic. This guidance is directly reflected in the verification steps above.
Delivery & Retry Policy
ShipTime expects your endpoint to acknowledge webhook deliveries by returning an HTTP 2xx response.
If a delivery attempt fails, ShipTime automatically retries the webhook using an exponential backoff schedule.
Succesful Delivery
A webhook delivery is considered successful when your endpoint returns any HTTP 2xx status code.
Once a delivery succeeds, no further retry attempts are made.
Retry schedule
| Retry after attempt | Delay |
|---|---|
| 1 | 5 minutes |
| 2 | 30 minutes |
| 3 | 2 hours |
| 4 | 5 hours |
| 5 | 10 hours |
| 6–8 | 24 hours |
After the final retry attempt, the event is marked as permanently failed and no additional delivery attempts are made.
Retry Conditions
A webhook delivery is retried when:
- Your endpoint returns a non-2xx HTTP status code.
- The request times out.
- A network error prevents the request from being delivered.
Duplicate Deliveries
Webhook endpoints should be idempotent.
Although ShipTime retries only when necessary, the same event may be delivered more than once. Use the event id to detect and ignore duplicate events.
Important
Always treat webhook deliveries as at least once. Your application should safely handle duplicate deliveries by storing processed event IDs and ignoring events that have already been processed.
Response Time
Return a successful HTTP response as quickly as possible.
If additional processing is required, acknowledge the webhook first and perform the remaining work asynchronously.
Failed Subscriptions
If a subscription repeatedly fails to receive webhook deliveries, ShipTime may disable the subscription to protect system resources.
Once the underlying issue has been resolved, you can reactivate the subscription by updating it or registering the same URL again.
Best practices
Follow these recommendations to build a reliable and secure webhook integration.
- Verify every webhook signature before processing the request.
- Use HTTPS for all webhook endpoints.
- Return a 2xx response quickly and process events asynchronously when possible.
- Treat webhook deliveries as idempotent by storing processed event IDs and ignoring duplicates.
- Protect your webhook secret and rotate it immediately if you suspect it has been compromised.
- Monitor failed deliveries and reactivate subscriptions after resolving any issues.
- Subscribe only to the event types you need to reduce unnecessary processing.
Next Steps
You're now ready to integrate ShipTime webhooks into your application.
A typical integration follows these steps:
- Create a webhook subscription.
- Store the returned webhook secret securely.
- Configure your endpoint to receive webhook requests.
- Verify the request signature.
- Process the webhook event.
- Return an HTTP 2xx response.
- Monitor deliveries and handle retries as needed.
For additional support, contact our ShipTime Tech Support Team via: techsupport@shiptime.com