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-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 typeDescriptionPayload notes
shipment.status_changedSent 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_updatedSent 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

FieldTypeRequiredDescription
urlstringrequiredHTTPS endpoint that will receive webhook events. Maximum 512 characters.
eventTypestring[]optionalEvents to subscribe to. Omit or provide an empty array to receive all events.
descriptionstringoptionalOptional 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 secret is 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}

FieldTypeRequiredDescription
urlstringrequiredThe endpoint URL.
eventTypestring[]optionalReplaces the existing event filter.
descriptionstringoptionalUpdates the subscription label.
activebooleanoptionalSet 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 PUT and 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

FieldDescription
idUnique event ID. Use this value as your deduplication key, as the same event may be delivered more than once.
typeThe webhook event type.
shipment_idThe ShipTime shipment ID. Use this to correlate the event with the shipment created through the ShipTime API.
occurred_atISO-8601 UTC timestamp of when the event occured
data.previous_statusStatus before the transition (may be null for the first event).
data.new_statusShipment status after the transition
data.tracking_numberCarrier 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:

HeaderExamplePurpose
X-ShipTime-Signaturet=1748908800,v1=a3f8c2…Timestamp and HMAC-SHA256 signature used to verify the request.
X-ShipTime-Event-Idb3d9a1f2-4c8e-…Matches the eventid.  Use as an idempotency key.
X-ShipTime-Delivery-Attempt0Zero-based delivery attempt number.
X-ShipTime-Event-Typeshipment.status_changedEvent-type hint. See the note below.
User-AgentShipTime-Webhooks/1Identifies 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 (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, 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 attemptDelay
15 minutes
230 minutes
32 hours
45 hours
510 hours
6–824 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:

  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 our ShipTime Tech Support Team via: techsupport@shiptime.com