Label Tracking Webhook
Label Tracking Webhook lets your system receive the latest normalized tracking snapshot instead of polling Get Shipping Label Track continuously.
The current event is label.tracking.updated.v1. Its initial delivery is sent only after an API-key-created OpenAPI Label is printable and both its tracking number and shipping-label content are available. Later deliveries are sent when the tracking pipeline publishes a normalized update for that Label. Delivery is scoped to the Label owner's company and environment (TEST or LIVE).
Subscription setup
Webhook subscriptions are currently configured through the company administrator management capability. Public OpenAPI subscription CRUD endpoints are not available in 2026-05. Save the signing_secret returned when the subscription is created: it is required to verify deliveries and is not included in subsequent events.
Event delivery
Your endpoint receives an HTTPS POST with Content-Type: application/json. A 2xx response marks the delivery as successful. All other HTTP responses and network errors are treated as failed attempts.
| Header | Description |
|---|---|
X-ZhenHub-Event-Id | Stable opaque identifier of the business event. Store it and process each event ID idempotently; do not parse or depend on its internal structure. |
X-ZhenHub-Event-Type | Always label.tracking.updated.v1 for this document version. |
X-ZhenHub-Delivery-Id | Identifier of this delivery record. It is useful for support and delivery diagnostics; do not use it as the idempotency key. |
X-ZhenHub-Timestamp | ISO 8601 offset timestamp used to create the signature. |
X-ZhenHub-Signature | v1= followed by a lowercase hexadecimal HMAC-SHA256 signature. |
The endpoint must use https, port 443, have no URL user-info, and resolve only to public addresses. Loopback, link-local, site-local, multicast and IPv6 unique-local addresses are rejected. Redirects are not followed.
Event payload
json
{
"id": "evt_8af350ed17678eb0858d06d7e5ac4436a448d8a20fa885540295251a469058f3",
"type": "label.tracking.updated.v1",
"occurred_at": "2026-09-04T10:30:05Z",
"data": {
"label_id": "123456",
"tracking": {
"tracking_courier": "ups",
"tracking_number": "92612902******9",
"outbound_time": null,
"delivered_time": "2026-09-04T09:06:00Z",
"checkpoints": [
{
"local_time": "2026-09-04T09:06:00Z",
"status": "delivered",
"sub_status": null,
"details": "Cincinnati,US",
"description": "Delivered"
}
]
}
}
}id is an opaque event identifier. Store it as an idempotency key, but do not parse it or infer event type, Label ID, timestamps or other business information from its value.
data.label_id is the Label ID returned by Create Shipping Label. data.tracking has the same schema and status semantics as the data object returned by Get Shipping Label Track, including explicit null values before carrier allocation and checkpoints sorted newest first. Each checkpoint includes the same sub_status field and nullable semantics documented in the Checkpoint sub-status reference. Carrier-specific event codes are not exposed. The webhook contains a full snapshot, not only the changed checkpoint; replace or reconcile your stored tracking view using the snapshot.
Historical failed attempts and exceptions remain in the full snapshot after recovery or successful delivery. Their presence does not mean that the issue is still active or has happened again. Evaluate the carrier event timeline using checkpoints[].local_time; do not reopen an exception solely because an older checkpoint is present in a new webhook. A normal delivery after an earlier failed attempt remains delivered, not delivered_with_exception. See Temporary delivery issue followed by successful delivery.
occurred_at is the time ZhenHub generated the webhook payload. It is serialized in UTC with second precision and a Z suffix, for example 2026-09-04T07:23:33Z. For the carrier event time, use tracking.checkpoints[].local_time.
The initial snapshot normally contains an information_received checkpoint generated by ZhenHub. It confirms that the Label and tracking number are ready; it does not mean that the carrier has physically received the package.
Verify the signature
Verify the signature before parsing or acting on the body. The signed value is the UTF-8 bytes of:
text
X-ZhenHub-Timestamp + "." + raw HTTP request bodyCompute HMAC-SHA256 with the subscription signing_secret, hex-encode it in lowercase, and compare it in constant time with X-ZhenHub-Signature after the v1= prefix.
python
import hashlib
import hmac
signed_payload = timestamp.encode("utf-8") + b"." + raw_body
expected = "v1=" + hmac.new(
signing_secret.encode("utf-8"), signed_payload, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, received_signature):
raise ValueError("Invalid webhook signature")Use the unmodified raw request body. Serializing parsed JSON again can change bytes and invalidate the signature. Also reject timestamps outside the replay window appropriate for your system.
Reliability and idempotency
- Delivery is at least once. Store
X-ZhenHub-Event-Iddurably and return2xxfor already-processed events. - Delivery is retried only for network failures,
408,429and5xxresponses, with at most six total attempts. Other4xxresponses and all3xxresponses are marked failed without an automatic retry. - Retries use capped exponential backoff with full jitter. For a
429, a validRetry-Aftervalue (seconds or HTTP date, up to 24 hours) is honored when it is later than the calculated backoff. - Each retry has a new timestamp and signature, while the event ID remains the same. The delivery ID identifies the delivery record and may be used to investigate attempts.
- The same endpoint URL receives at most one request per second across all webhook subscriptions. After five consecutive retryable failures, deliveries for the affected subscription are paused for one hour before pending events may resume.
- Return
2xxquickly, then process the event asynchronously. Continue to use Get Shipping Label Track for reconciliation or recovery.
Current implementation assessment
This follows the mainstream webhook baseline used by platforms such as Stripe, GitHub and Shopify: versioned event type, HTTPS push, HMAC over timestamp plus raw body, no redirects, stable event ID, asynchronous retries, endpoint throttling and consumer-side idempotency. The full tracking snapshot is also a common and practical design for state convergence.
It is not yet a full production-grade webhook platform. Public subscription CRUD, delivery history, replay and DLQ APIs are not exposed, and receiver-based end-to-end tests are still required before committing a broad delivery SLA.
sub_status uses the same eleven cross-carrier normalized reasons as the tracking reference. It may be null even for an exception; use status and description as fallback. Clients must tolerate unknown future values. Carrier-specific explanations stay in description.
weather_disruption is preserved when supplied by a reliably mapped source. It does not change the checkpoint status or make a later normal delivery an exceptional delivery.
