# Tracking (Webhooks)

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.com` to 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.


## API Specification

A complete **AsyncAPI 3.0 specification** is available for the webhook events:

- **Specification File:** [`api/v3.1/asyncapi.yaml`](/api/v3.1/asyncapi)
- **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


## Setup

To enable webhook tracking:

1. **Provide your webhook URL** - This should be a publicly accessible HTTPS endpoint
2. **Configure event types** - Choose which tracking events you want to receive (see below)
3. **Implement endpoint** - Your endpoint must accept HTTP POST requests with JSON payloads
4. **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.

## Event Types

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


## Retry Policy

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 Structure

### Webhook Payload

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.

#### Root Event Object

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


#### Complete Example Payload


```json
{
  "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
}
```

### Date and Time Formats

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.

### Key Message Fields

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 |


### Understanding Checkpoints

The `checkpoints` array contains the complete tracking history from the carrier. Each checkpoint represents a scan or status update:


```json
{
  "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


## Implementation Best Practices

### 1. Idempotency

Implement idempotent webhook processing using the `event_id` field:


```javascript
// 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.

### 2. Asynchronous Processing

Respond to webhooks immediately, then process asynchronously:


```javascript
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.

### 3. Validation

Validate incoming webhook payloads against the AsyncAPI schema:

- Verify required fields are present (`event`, `event_id`, `msg`, `ts`)
- Check that `event` equals `"tracking_update"`
- Validate that `msg.tracking_number` matches expected format
- Consider implementing webhook signature verification (contact support)


### 4. Error Handling

Implement graceful error handling:


```javascript
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.

### 5. Monitoring

Monitor your webhook endpoint health:

- Track webhook delivery success/failure rates
- Monitor processing latency
- Alert on consecutive failures
- Log all `event_id` values for troubleshooting
- Track `tag` distribution to understand delivery patterns


### 6. Testing

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


### 7. Database Design

Consider storing:

- Complete webhook payload (for audit trail)
- `event_id` (for idempotency)
- `tracking_number` (indexed for lookups)
- `order_id` and `order_number` (indexed for customer queries)
- `tag` (for filtering/reporting)
- `checkpoints` array (for detailed history)
- Processing status and timestamps


## Webhook vs. Polling

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


## Schema Documentation

For complete field definitions and data types, refer to:

- **AsyncAPI Specification:** [`api/v3.1/asyncapi.yaml`](/api/v3.1/asyncapi) - Complete webhook schema
- **TrackingMessage Schema:** Components > Schemas > TrackingMessage
- **Checkpoint Schema:** Components > Schemas > Checkpoint
- [AfterShip Tracking API Documentation](https://www.aftership.com/docs/tracking/model/tracking) - Additional reference
- [AfterShip Checkpoint API Documentation](https://www.aftership.com/docs/tracking/model/checkpoint) - Checkpoint details


## Getting Help

If you need assistance with webhook implementation:

- Review the AsyncAPI specification for complete schema details
- Email `api@wineshipping.com` for:
  - Webhook endpoint configuration
  - Sample event data specific to your account
  - Webhook signature implementation details
  - Troubleshooting delivery issues