Skip to content
English - Canada
  • There are no suggestions because the search field is empty.

ShipTime Webhooks — Developer Guide

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? See the ShipTime API documentation for details on obtaining API credentials.

Contents

  • Overview
  • Quickstart
  • Event types
  • Subscriptions API
  • Authentication
  • Create a subscription
  • List subscriptions
  • Get a single subscription
  • Update a subscription
  • Delete a subscription
  • Receiving events
  • Payload format
  • Delivery headers
  • Verifying signatures
  • Delivery & Retry Policy
  • Best practices

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-Signature header.
  • Returns an HTTP 200 OK response 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 when a shipment status changes, from creation (PENDING) through PENDING → IN_TRANSIT, IN_TRANSIT → DELIVERED, or cancellation. 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.

Endpoint

POST /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. The secret is never included in list or get responses.

Response

200 OK
[   {     "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.

Endpoint

GET /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.

Endpoint

DELETE /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 PUT and set "active": true, or
  • Re-register the same URL using 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 occurred.
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 event ID. 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 type field.

The X-ShipTime-Event-Type header currently always reports shipment.status_changed, even for shipment.tracking_updated events. 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:

  • t is the Unix timestamp in seconds.
  • v1 is the HMAC-SHA256 signature.

Verification Process

To verify a webhook request:

  1. Parse the t and v1 values from the X-ShipTime-Signature header.
  2. Build the signed message by concatenating:
    • t
    • a literal .
    • the raw request body — do not re-serialize the JSON.
  3. Compute the HMAC-SHA256 digest using your webhook secret.
  4. Compare the computed digest with v1 using a constant-time comparison.
  5. 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, timefrom flask import request, abortdef 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.

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.

Successful 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

  • 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:

  1. Create a webhook subscription.
  2. Store the returned webhook secret securely.
  3. Configure your endpoint to receive webhook requests.
  4. Verify the request signature.
  5. Process the webhook event.
  6. Return an HTTP 2xx response.
  7. Monitor deliveries and handle retries as needed.

For additional support, contact the ShipTime Technical Support team at techsupport@shiptime.com.