---
title: "Register webhook (billable)"
method: POST
path: "/v2/webhooks/register"
tags: ["Webhooks"]
---

# Register webhook (billable)

`POST /v2/webhooks/register`

Register a webhook for parcel tracking. Supports 2 modes:
- `recurring: true` — Continuous monitoring until delivery (subscription). **Recommended for most integrations.** Register once and receive push notifications whenever the tracking status changes. Ideal for keeping your database in sync with delivery progress.
- `recurring: false` — Query once only (one-time, default). Useful for one-off lookups where you don't need ongoing updates.

**Why use webhooks?** Polling `/v2/track` repeatedly wastes your request quota. A single parcel typically has 5–10 status changes over its lifecycle. With webhooks, you receive only those 5–10 updates instead of making hundreds of polling requests.

Billable API, supports up to 100 items per registration.

**What we POST to your endpoint.** Content-Type is `application/json`; we expect a 2xx response within 10 seconds. Redirects are not followed.


  ```json
  {
    "event": "tracking.updated",
    "timestamp": "2026-08-21T04:15:22.184Z",
    "data": {
      "requestId": "req_abc123",
      "timestamp": "2026-08-21T04:15:22.100Z",
      "trackingItems": [
        {
          "carrier": { "code": "us.usps", "name": "USPS", "country": "US" },
          "trackingNumber": "9400111206206406260787",
          "status": "success",
          "currentStatus": "in_transit",
          "hasChange": true,
          "trackingData": { "deliveryStatus": "in_transit", "events": [] }
        }
      ],
      "isPeriodicUpdate": true,
      "hasChanges": true,
      "changedItemCount": 1,
      "queriedItemCount": 1,
      "deliveredItemCount": 0,
      "changeId": "chg_xyz789"
    }
  }
  ```


**Fields:**

`event` — `tracking.registered` on the first lookup after you register, `tracking.updated` on every later poll.

`data.requestId` — the subscription id returned by this endpoint.

`data.trackingItems[]` — one entry per tracked parcel. `carrier` is an object (`code`, `name`, `country`), not a string. `status` is `success` or `error`. `currentStatus` is the standardized delivery status. `trackingData` holds the full result. `hasChange` marks items whose status changed in this cycle.

`data.isPeriodicUpdate`, `hasChanges`, `changedItemCount`, `queriedItemCount`, `deliveredItemCount`, `changeId` — present only on recurring updates, not on the first `tracking.registered` delivery.

**Headers:** `X-WhereParcel-Event`, `X-WhereParcel-Timestamp`, and `X-WhereParcel-Signature` — an HMAC-SHA256 hex digest of the exact raw JSON body, keyed with your endpoint secret. Verify it by recomputing the digest over the raw body before parsing.

**If you register without `webhookEndpointId`, nothing is pushed to you.** That is the pull mode: retrieve results with `GET /v2/webhooks/subscriptions/{requestId}` or `POST /v2/webhooks/results`. To receive the payload above, create an endpoint with `POST /v2/webhook-endpoints` and pass its id as `webhookEndpointId`.

## Request body

- RegisterWebhookRequest — Unified webhook registration request (Bulk + Recurring support) Usage patterns: 1. Bulk + Recurring: { webhookEndpointId, trackingItems: [...], recurring: true } 2. Bulk + One-time: { trackingItems: [...], recurring: false, webhookEndpointId?: ... } 3. Bulk + One-time (no webhook): { trackingItems: [...], recurring: false }
  - `trackingItems` object[], required — Tracking items (up to 100)
    - `carrier` string, required
    - `trackingNumber` string, required
    - `clientId` string
    - `postalCode` string
    - `phoneNumber` string
  - `recurring` boolean — Tracking mode (optional, default: false) - false: Query once only (default, safe) → webhookEndpointId optional - true: Continuous monitoring (explicit request required) → webhookEndpointId required
  - `webhookEndpointId` string — Pre-registered Webhook Endpoint ID - Required when recurring: true (validated by middleware) - Optional when recurring: false - If provided, sends POST on completion; if omitted, no notification sent - Must register a Webhook Endpoint first

## Response `200`

Webhook registered successfully

- union
  - RegisterWebhookRecurringResponse — Recurring mode response (subscription) Returned when recurring: true
    - `mode` string, required
    - `requestId` string, required
    - `trackingItemCount` number, required
    - `webhookEndpointId` string, required
    - `createdAt` string, required
  - RegisterWebhookOneTimeResponse — One-time mode response Returned when recurring: false
    - `mode` string, required
    - `requestId` string, required
    - `webhookEndpointId` string
    - `webhookDelivered` boolean, required
    - `progress` object, required
      - `total` number, required
      - `completed` number, required
      - `succeeded` number, required
      - `failed` number, required
      - `percentage` number, required
    - `results` TrackingResultItem[], required
      - `carrier` string, required
      - `trackingNumber` string, required
      - `clientId` string
      - `status` 'failed' | 'success', required
      - `data` TrackingData — Tracking data (full delivery information) Contains complete delivery information for a specific tracking number. All fields are converted to WhereParcel standard format.
        - `deliveryStatus` 'pending' | 'in_transit' | 'out_for_delivery' | 'delivered' | 'failed' | 'returned' | 'cancelled' | 'unknown', required — Delivery status (standardized value) The standard delivery status defined by WhereParcel. The various status codes of all carriers are converted into one of these 8 values.
        - `estimatedDelivery` string — Estimated delivery date/time (optional, ISO 8601 format) The estimated delivery date/time provided by the carrier. When delivery is complete, this represents the actual delivery time. Note: The meaning of this field varies by status: - status is "delivered": Actual delivery completion time - status is other: Estimated delivery date/time (subject to change) Examples: - "2026-01-30T18:00:00+09:00" (Korea timezone) - "2026-01-30T14:00:00-05:00" (US Eastern timezone) Note: Some carriers may not provide estimated delivery dates.
        - `events` TrackingEvent[], required — Delivery event list Chronologically sorted delivery history. The most recent event is at the end of the array. Examples: - events[0]: First event (oldest record) - events[events.length - 1]: Last event (most recent record)
          - `timeUnknown` boolean — Whether the time portion of `timestamp` is an estimate rather than a value the carrier provided (optional). Some carriers give only a **date** for certain events and no scan time — USPS does this for events like "In Transit to Next Facility" and "Pre-Shipment". In that case `timestamp` is filled with noon on that date to stay valid ISO 8601, and this field is `true`. Absent or `false` means the carrier supplied the time itself. Without this flag a filled-in noon is indistinguishable from a real scan at noon, which is why several events on the same date can look like they share one timestamp.
          - `timestamp` string, required — Event timestamp (ISO 8601 format) Standardized time by WhereParcel. Converts various carrier time formats to ISO 8601 (e.g., "2026-01-30T14:30:00Z"). Examples: - Korean carrier: "2026-01-30 14:30" → "2026-01-30T14:30:00+09:00" (KST) - USPS: "January 30, 2026, 2:30 pm EST" → "2026-01-30T14:30:00-05:00" (EST) - DHL: "30.01.2026 14:30" → "2026-01-30T14:30:00Z"
          - `status` 'pending' | 'in_transit' | 'out_for_delivery' | 'delivered' | 'failed' | 'returned' | 'cancelled' | 'unknown', required — Delivery status (standardized value) The standard delivery status defined by WhereParcel. The various status codes of all carriers are converted into one of these 8 values.
          - `location` string — Location information (WhereParcel standardized, optional) The physical location where the event occurred. Standardizes different carrier location formats as much as possible. Examples: - Korea: "Gangnam-gu, Teheran-ro, Seoul", "Buk-gu Logistics Center, Busan" - USPS: "NEW YORK NY DISTRIBUTION CENTER" - FedEx: "MEMPHIS, TN" - DHL: "Frankfurt Gateway" Note: Some carriers may not provide location information.
          - `description` string, required — Event description (WhereParcel standardized) Detailed description of the event. Translated to the user's language when possible. Examples: - Korea: "Arrived at Namgwanak" - FedEx: "Delivered. Signed by JOHN DOE" - USPS: "Out for Delivery" - DHL: "Customs clearance completed"
          - `rawStatus` string — Carrier original status code (optional) Preserves the original status code/text as displayed by the carrier. Provided so customers can compare with the carrier's official website. Examples: - UPS: "M" (Manifest), "I" (In Transit), "D" (Delivered) - USPS: "Acceptance", "In Transit", "Delivered" - FedEx: "PU" (Picked up), "IT" (In transit), "DEL" (Delivered) - DHL: "Picked up", "Clearance event", "Delivered" Note: This field matches the "Status" column on the carrier's official website.
          - `rawDescription` string — Carrier original description (optional) Preserves the original event description as displayed by the carrier. Unlike description, provided as-is without language conversion or standardization. Examples: - UPS: "Your package was delivered. Signed by: JOHN" - USPS: "Out for Delivery, Expected Delivery by 8:00pm" - FedEx: "Delivered. Left at front door. Signature Service not requested." - DHL: "Clearance event - Import clearance completed" - China carrier: "【广州市】已到达 广州转运中心" (original Chinese preserved) Note: This field matches the "Details" column on the carrier's official website.
        - `from` object — Sender information (optional) Location information where the parcel was shipped from. Names may be masked for privacy protection. Examples: - name: "Hong*dong" (individual: masked), "Coupang" (business: original) - address: "Gangnam-gu, Seoul", "Tokyo, Japan" Note: Some carriers may not provide sender information.
          - `name` string
          - `address` string
        - `to` object — Recipient information (optional) Location information where the parcel will be delivered. Names and detailed addresses may be masked for privacy protection. Examples: - name: "Kim*su" (individual: masked), "ABC Company" (business: original) - address: "Seocho-gu, Seoul" (detailed address excluded) Note: Some carriers may not provide recipient information.
          - `name` string
          - `address` string
        - `signedBy` string — Signature information (optional) The name of the person who signed upon delivery completion. Primarily provided by North American/European carriers (FedEx, UPS, DHL, etc.). Examples: - "JOHN DOE" - "Front Desk" - "Receptionist" Note: - Most Korean/Asian carriers do not provide this. - Unattended deliveries (e.g., left at door) will be null.
        - `weight` object — Weight information (optional) The weight of the parcel. Primarily provided for international shipments. Examples: - { value: 2.5, unit: "kg" } - { value: 5.5, unit: "lb" } Note: Most domestic carriers do not provide weight information.
          - `value` number, required
          - `unit` 'g' | 'kg' | 'lb', required
        - `deliveryInstructions` string — Delivery instructions (optional) Customer-requested delivery instructions. Examples: - "Leave at security desk if absent" - "Leave at front door" - "Call before delivery" Note: Most carriers do not provide this.
        - `lastUpdated` string, required — Last updated date/time (ISO 8601 format) The time when WhereParcel retrieved data from the carrier. Used to determine the freshness of cached data. Example: - "2026-01-30T15:30:00Z" Note: This is not the carrier's last update time.
      - `error` ApiError — API error (common)
        - `code` string, required
        - `message` string, required

## Other responses

- `401` — Authentication failed
- `429` — Rate limit exceeded

---

[API](https://skmtc.dev/whereparcel/apis/whereparcel-api.md) · [All operations](https://skmtc.dev/whereparcel/apis/whereparcel-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/whereparcel/whereparcel-api/revisions/629880970b8b/schema)
