Wineshipping provides real-time shipment tracking updates through webhooks, enabling you to receive instant notifications about package status changes without polling our API.
Getting Started: Send an email to
api@wineshipping.comto enable your event-driven feed and receive bespoke samples of tracking events for your account, include webhook url as well as one for testing if your environment supports that.
A complete AsyncAPI 3.0 specification is available for the webhook events:
- Specification File:
api/v3.1/asyncapi.yaml - Operation:
receiveTrackingUpdate
The AsyncAPI specification provides:
- Complete message schemas with all fields documented
- Event payload validation rules
- Example payloads for each event type
- Code generation capabilities for webhook handlers
- Mock server generation for testing
To enable webhook tracking:
- Provide your webhook URL - This should be a publicly accessible HTTPS endpoint
- Configure event types - Choose which tracking events you want to receive (see below)
- Implement endpoint - Your endpoint must accept HTTP POST requests with JSON payloads
- Return success status - Respond with HTTP status codes 200-299 to acknowledge receipt
Important: Your webhook endpoint must respond within a reasonable timeout (recommended: < 10 seconds) and should process events asynchronously to avoid blocking the webhook delivery.
Available events include the following. We can configure your event feed to include all or a subset of these event types. The tag field in the posted event data indicates the event type:
- Pending: New tracking number that is pending to track with last-mile carrier
- InfoReceived: New tracking number; the last-mile carrier has received the request and is about to pick up the shipment
- InTransit: The shipment is on its way and has left the warehouse
- OutForDelivery: Carrier is about to deliver the shipment, or it is ready for pickup
- AttemptFail: Carrier attempted to deliver but failed, usually leaves a notice and will try to deliver again
- Delivered: The shipment was delivered successfully
- AvailableForPickup: The package arrived at a pickup point near the recipient and is available for pickup
- Exception: Hold, undelivered, returned shipment to sender, or any shipping exceptions
- Expired: Shipment has no tracking information for 30 days since added
In case of an unsuccessful event when calling your URL (HTTP response code NOT between 200 and 299), Wineshipping attempts to deliver your webhooks for up to 14 times with exponential backoff using the formula: 2^(number of retry) × 30s
| Attempt | Retry # | Delay (seconds) | Cumulative Delay | Time Elapsed |
|---|---|---|---|---|
| 1 | 0 | 0 | 0 sec | Immediate |
| 2 | 1 | 30 | 30 sec | 30 seconds |
| 3 | 2 | 60 | 90 sec | 1.5 minutes |
| 4 | 3 | 120 | 210 sec | 3.5 minutes |
| 5 | 4 | 240 | 450 sec | 7.5 minutes |
| 6 | 5 | 480 | 930 sec | 15.5 minutes |
| 7 | 6 | 960 | 1,890 sec | 31.5 minutes |
| 8 | 7 | 1,920 | 3,810 sec | ~1 hour |
| 9 | 8 | 3,840 | 7,650 sec | ~2.1 hours |
| 10 | 9 | 7,680 | 15,330 sec | ~4.3 hours |
| 11 | 10 | 15,360 | 30,690 sec | ~8.5 hours |
| 12 | 11 | 30,720 | 61,410 sec | ~17 hours |
| 13 | 12 | 61,440 | 122,850 sec | ~34 hours |
| 14 | 13 | 122,880 | 245,730 sec | ~68 hours |
Key Points:
- If the first attempt fails, the 2nd attempt occurs 30 seconds later
- If the 7th attempt fails, the 8th attempt occurs 1,920 seconds (32 minutes) later
- After 14 failed attempts (~68 hours), the webhook delivery is permanently abandoned
- Monitor your webhook endpoint's uptime and performance to avoid missing events
Event data is posted to your URL via HTTP POST in JSON format. Each webhook event follows the structure defined in the AsyncAPI specification's TrackingUpdatePayload schema.
| Field | Type | Description |
|---|---|---|
event | string | Always "tracking_update" for tracking events |
event_id | string | Unique UUID for this webhook event |
is_tracking_first_tag | boolean | Indicates if this is the first tracking event for shipment |
msg | object | The tracking message payload (see below) |
ts | integer | Unix timestamp when the event was generated |
{
"event": "tracking_update",
"event_id": "555e9daa-f6ca-48a2-b30c-48e23cd0d0d6",
"is_tracking_first_tag": true,
"msg": {
"active": true,
"aftership_estimated_delivery_date": {
"estimated_delivery_date": "2026-07-22",
"confidence_score": null,
"estimated_delivery_date_min": "2026-07-22",
"estimated_delivery_date_max": "2026-07-23"
},
"android": [],
"checkpoints": [
{
"checkpoint_time": "2026-07-20T15:00:15-05:00",
"city": null,
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-20T20:00:16+00:00",
"location": "US, United States",
"message": "Shipment Ready for UPS",
"raw_tag": "MP",
"slug": "ups",
"state": null,
"subtag": "InfoReceived_001",
"subtag_message": "Info Received",
"tag": "InfoReceived",
"zip": null
},
{
"checkpoint_time": "2026-07-20T15:00:15-05:00",
"city": null,
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-20T20:12:01+00:00",
"location": "United States",
"message": "Updated Delivery Time",
"raw_tag": "UT",
"slug": "ups",
"state": null,
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"tag": "InTransit",
"zip": null
},
{
"checkpoint_time": "2026-07-20T19:20:01-05:00",
"city": "Earth City",
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-21T00:20:56+00:00",
"location": "Earth City, MO, US, United States",
"message": "On the Way",
"raw_tag": "OR",
"slug": "ups",
"state": "MO",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"tag": "InTransit",
"zip": null
},
{
"checkpoint_time": "2026-07-20T19:20:01-05:00",
"city": null,
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-21T00:35:25+00:00",
"location": "United States",
"message": "Updated Delivery Date",
"raw_tag": "UP",
"slug": "ups",
"state": null,
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"tag": "InTransit",
"zip": null
},
{
"checkpoint_time": "2026-07-20T22:09:00-05:00",
"city": "Earth City",
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-21T03:20:46+00:00",
"location": "Earth City, MO, US, United States",
"message": "On the Way",
"raw_tag": "DP",
"slug": "ups",
"state": "MO",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"tag": "InTransit",
"zip": null
},
{
"checkpoint_time": "2026-07-21T14:59:00-04:00",
"city": "Orlando",
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-21T19:16:38+00:00",
"location": "Orlando, FL, US, United States",
"message": "On the Way",
"raw_tag": "AR",
"slug": "ups",
"state": "FL",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"tag": "InTransit",
"zip": null
},
{
"checkpoint_time": "2026-07-21T23:04:00-04:00",
"city": "Orlando",
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-22T03:19:39+00:00",
"location": "Orlando, FL, US, United States",
"message": "On the Way",
"raw_tag": "DP",
"slug": "ups",
"state": "FL",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"tag": "InTransit",
"zip": null
},
{
"checkpoint_time": "2026-07-22T00:50:00-04:00",
"city": "Clearwater",
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-22T05:05:31+00:00",
"location": "Clearwater, FL, US, United States",
"message": "On the Way",
"raw_tag": "AR",
"slug": "ups",
"state": "FL",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"tag": "InTransit",
"zip": null
},
{
"checkpoint_time": "2026-07-22T05:18:16-04:00",
"city": "Clearwater",
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-22T09:19:53+00:00",
"location": "Clearwater, FL, US, United States",
"message": "Preparing for Delivery",
"raw_tag": "YP",
"slug": "ups",
"state": "FL",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"tag": "InTransit",
"zip": null
},
{
"checkpoint_time": "2026-07-22T10:11:07-04:00",
"city": "Clearwater",
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-22T14:11:17+00:00",
"location": "Clearwater, FL, US, United States",
"message": "Out for Delivery",
"raw_tag": "OT",
"slug": "ups",
"state": "FL",
"subtag": "OutForDelivery_001",
"subtag_message": "Out for Delivery",
"tag": "OutForDelivery",
"zip": null
},
{
"checkpoint_time": "2026-07-22T11:06:54-04:00",
"city": "BELLEAIR",
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-22T15:08:24+00:00",
"location": "BELLEAIR, FL, 33756, US, United States",
"message": "Delivery Attempted",
"raw_tag": "48",
"slug": "ups",
"state": "FL",
"subtag": "AttemptFail_001",
"subtag_message": "Failed Attempt",
"tag": "AttemptFail",
"zip": "33756"
},
{
"checkpoint_time": "2026-07-22T11:08:44-04:00",
"city": "BELLEAIR",
"coordinates": [],
"country_iso3": "USA",
"country_name": "United States",
"created_at": "2026-07-22T15:10:23+00:00",
"location": "BELLEAIR, FL, 33756, US, United States",
"message": "Delivered",
"raw_tag": "KB",
"slug": "ups",
"state": "FL",
"subtag": "Delivered_001",
"subtag_message": "Delivered",
"tag": "Delivered",
"zip": "33756"
}
],
"courier_connection_id": null,
"courier_destination_country_iso3": "USA",
"courier_redirect_link": "https://www.ups.com/track?loc=en_US&tracknum=1Z59E877A871526000&requester=WT/trackdetails",
"courier_tracking_link": "https://www.ups.com/track?loc=en_US&tracknum=1Z59E877A871526000&requester=WT/trackdetails",
"created_at": "2026-07-20T16:22:03+00:00",
"custom_estimated_delivery_date": null,
"custom_fields": {
"contents": "1xI0001003686(EACH) | 2xZ1883530252(750) | 1xZ2081643956(750) | 2xZ2266944449(750) | 6xZ2477531880(750) | 1xZ2512530726(750)",
"contents_collateral_units": "1",
"contents_merch_units": "0",
"contents_product_units": "12",
"date_dc": "07/18/2023 16:35:31",
"date_input": "2026-07-18",
"dc_cid": "CONT-013922021",
"dc_oid": "14039199",
"dc_pool": "DAILY",
"dc_ship_confirm": "2026-07-20T22:54:40+00:00",
"dc_ver": "v3",
"final_mile": "ups",
"oms_contents": "1xI0001003686(EACH) | 2xZ1883530252(750) | 1xZ2081643956(750) | 2xZ2266944449(750) | 6xZ2477531880(750) | 1xZ2512530726(750)",
"regulatory_attributes": "FL:123456789",
"rsd": "2026-07-19",
"seller_name": "The Winery Name",
"seller_number": "10000",
"shipfrom_dc": "ETC01",
"special_attributes": "icepack",
"transmission_id": "OiHTFObyOOasdd",
"transport_zone": "5",
"tref": "f6c39d42-4afe-4957-9ad3-ea099d492b7c"
},
"custom_fields": {
"container_type_code": "750STNDUP12BTL",
"contents": "1xF09351(750) | 1xI200OFF-1223(EACH) | 1xI2309WIQ4COUP(EACH) | 1xI2309WITY(EACH) | 1xI24WIMRG-04(EACH) | 1xN01425(750) | 1xN01436(750) | 1xN01457(750) | 2xN01512(750) | 1xN01519(750) | 1xR10555(750) | 1xR10576(750) | 1xR10608(750) | 2xW21651857(750)",
"contents_collateral_units": "4",
"contents_merch_units": "0",
"contents_product_units": "12",
"date_packed": "2024-04-23T00:23:41+00:00",
"dc_carrier": "FEX-GRND",
"dc_oid": "17892793",
"dc_pool": "DAILY",
"dc_ship_confirm": "2024-04-23T00:57:38+00:00",
"dc_ver": "v3",
"final_mile": "fedex",
"oms_contents": "1xF09351(750) | 1xI200OFF-1223(EACH) | 1xI2309WIQ4COUP(EACH) | 1xI2309WITY(EACH) | 1xI24WIMRG-04(EACH) | 1xN01425(750) | 1xN01436(750) | 1xN01457(750) | 2xN01512(750) | 1xN01519(750) | 1xR10555(750) | 1xR10576(750) | 1xR10608(750) | 2xW21651857(750)",
"regulatory_attributes": "",
"rsd": "2024-04-22",
"seller_name": "The Winery Name",
"seller_number": "10000",
"shipfrom_dc": "VCX01",
"special_attributes": "hold.location",
"special_instructions": "",
"transmission_id": "c15b079f-a1e5-4c4b-9ac1-3db3b4fb7555",
"transport_zone": "5",
"tref": "a8da0749-c6ad-4bbd-bf62-acaa2d814555"
},
"customer_name": "Jane D.",
"delivery_time": 3,
"delivery_type": null,
"destination_city": "Belleair",
"destination_country_iso3": "USA",
"destination_postal_code": "33756-1925",
"destination_raw_location": "555 Main Street, Belleair, FL, 33756-1925",
"destination_state": "FL",
"emails": ["jane@email.com"],
"expected_delivery": "2026-07-24",
"first_attempted_at": "2026-07-22T11:06:54-04:00",
"first_estimated_delivery": {
"datetime": "2026-07-24",
"datetime_max": null,
"datetime_min": null,
"source": "Carrier EDD",
"type": "specific"
},
"id": "rwfu4vriciwerlkbd1g0y00j",
"ios": [],
"language": "en",
"last_mile_tracking_supported": true,
"last_updated_at": "2026-07-22T15:10:23+00:00",
"latest_estimated_delivery": {
"source": "Carrier EDD",
"datetime": "2026-07-24",
"type": "specific",
"datetime_min": null,
"datetime_max": null
},
"next_couriers": [],
"note": null,
"on_time_difference": -2,
"on_time_status": "early",
"order_date": "2026-07-18T16:35:31-07:00",
"order_id": "CONT-013922021",
"order_id_path": null,
"order_number": "33516482",
"order_promised_delivery_date": null,
"order_tags": [],
"origin_city": "Earth City",
"origin_country_iso3": "USA",
"origin_postal_code": "630451224",
"origin_raw_location": "Earth City, Missouri",
"origin_state": "Missouri",
"path": "deprecated",
"pickup_location": null,
"pickup_note": null,
"return_to_sender": false,
"shipment_delivery_date": "2026-07-22T11:08:44-04:00",
"shipment_package_count": null,
"shipment_pickup_date": "2026-07-20T15:00:15-05:00",
"shipment_tags": [],
"shipment_type": "UPS Ground",
"shipment_weight": 34.6,
"shipment_weight_unit": "lb",
"signed_by": null,
"slug": "ups",
"smses": ["+18555072501"],
"source": "api",
"subscribed_smses": ["+18555072509", "+18555072501"],
"subscribed_emails": ["jane@email.com", "another_email@email.com"],
"subtag": "Delivered_001",
"subtag_message": "Delivered",
"tag": "Delivered",
"title": "33516482",
"tracked_count": 19,
"tracking_account_number": null,
"tracking_destination_country": "USA",
"tracking_key": null,
"tracking_number": "1Z59E877A871526000",
"tracking_origin_country": "USA",
"tracking_postal_code": "33756-1925",
"tracking_ship_date": "20230720",
"tracking_state": "FL",
"unique_token": "deprecated",
"updated_at": "2026-07-22T15:10:23+00:00"
},
"ts": 1690038623
}Dates and times provided by carriers vary in precision by event. Your webhook handler should be able to parse multiple formats:
YYYY-MM-DD- Date only (e.g.,2026-07-22)YYYY-MM-DDTHH:MM:SS- Date and time without timezone (e.g.,2026-07-22T11:08:44)YYYY-MM-DDTHH:MM:SS+TIMEZONE- Full ISO 8601 with timezone (e.g.,2026-07-22T11:08:44-04:00)
Recommendation: Use a robust date/time parsing library that handles multiple ISO 8601 formats automatically.
The most commonly used fields in the msg object for tracking applications:
| Field | Description |
|---|---|
tag | Primary tracking status (Delivered, InTransit, etc.) |
subtag | Detailed status code (e.g., Delivered_001) |
tracking_number | Carrier tracking number |
order_id | Your order identifier |
order_number | Customer-facing order number |
slug | Carrier identifier (ups, fedex, usps, etc.) |
checkpoints | Array of all tracking events from carrier |
last_updated_at | Most recent update timestamp |
shipment_delivery_date | Actual delivery timestamp (null until delivered) |
expected_delivery | Expected delivery date |
destination_* | Destination address fields (city, state, postal_code) |
custom_fields | Custom metadata specific to your shipment |
The checkpoints array contains the complete tracking history from the carrier. Each checkpoint represents a scan or status update:
{
"checkpoint_time": "2026-07-22T11:08:44-04:00",
"tag": "Delivered",
"subtag": "Delivered_001",
"message": "Delivered",
"location": "BELLEAIR, FL, 33756, US, United States",
"city": "BELLEAIR",
"state": "FL",
"zip": "33756"
}Checkpoints are ordered chronologically and can be used to:
- Display detailed tracking timeline to customers
- Calculate transit time between events
- Identify delivery exceptions or delays
- Determine exact delivery location
Implement idempotent webhook processing using the event_id field:
// Example: Check if event was already processed
if (await db.eventExists(payload.event_id)) {
return { statusCode: 200, body: 'Already processed' };
}
// Process the event
await processTrackingUpdate(payload);
// Store event_id to prevent duplicate processing
await db.saveEventId(payload.event_id);Why: Network issues or retries may cause the same event to be delivered multiple times.
Respond to webhooks immediately, then process asynchronously:
app.post('/webhooks/tracking', async (req, res) => {
// Validate and acknowledge immediately
res.status(200).json({ received: true });
// Queue for background processing
await queue.add('process-tracking', req.body);
});Why: Keeps webhook delivery fast and prevents timeouts during heavy processing.
Validate incoming webhook payloads against the AsyncAPI schema:
- Verify required fields are present (
event,event_id,msg,ts) - Check that
eventequals"tracking_update" - Validate that
msg.tracking_numbermatches expected format - Consider implementing webhook signature verification (contact support)
Implement graceful error handling:
try {
await processTrackingUpdate(payload);
return { statusCode: 200 };
} catch (error) {
// Log error for debugging
logger.error('Webhook processing failed', { error, event_id: payload.event_id });
// Return 200 if it's a client error (won't fix with retry)
if (error instanceof ValidationError) {
return { statusCode: 200, body: 'Invalid payload' };
}
// Return 5xx for server errors (will trigger retry)
return { statusCode: 500, body: 'Server error' };
}Important: Only return non-2xx status codes for temporary failures where a retry might succeed.
Monitor your webhook endpoint health:
- Track webhook delivery success/failure rates
- Monitor processing latency
- Alert on consecutive failures
- Log all
event_idvalues for troubleshooting - Track
tagdistribution to understand delivery patterns
Use the AsyncAPI specification to:
- Generate mock webhook servers for development
- Create test fixtures for each event type
- Validate your webhook handler against the schema
- Generate code stubs in your preferred language
Consider storing:
- Complete webhook payload (for audit trail)
event_id(for idempotency)tracking_number(indexed for lookups)order_idandorder_number(indexed for customer queries)tag(for filtering/reporting)checkpointsarray (for detailed history)- Processing status and timestamps
Webhooks offer significant advantages over polling the GetDetails API:
| Aspect | Webhooks (Event-Driven) | Polling (GetDetails API) |
|---|---|---|
| Latency | Real-time (seconds) | Delayed by polling interval |
| Efficiency | Push-based, no wasted requests | Many requests return no new data |
| Rate Limits | Not subject to rate limiting | Subject to API rate limits |
| Cost | No API calls required | Consumes API quota |
| Scalability | Scales with events | Scales with polling frequency × count |
| Recommended | Yes, for all implementations | Only for initial setup or backfill |
Recommendation: Use webhooks as your primary tracking mechanism and only use GetDetails API for:
- Initial historical data retrieval
- Backfilling missed events
- Manual troubleshooting
For complete field definitions and data types, refer to:
- AsyncAPI Specification:
api/v3.1/asyncapi.yaml- Complete webhook schema - TrackingMessage Schema: Components > Schemas > TrackingMessage
- Checkpoint Schema: Components > Schemas > Checkpoint
- AfterShip Tracking API Documentation - Additional reference
- AfterShip Checkpoint API Documentation - Checkpoint details
If you need assistance with webhook implementation:
- Review the AsyncAPI specification for complete schema details
- Email
api@wineshipping.comfor:- Webhook endpoint configuration
- Sample event data specific to your account
- Webhook signature implementation details
- Troubleshooting delivery issues