---
title: "Subscribe to an HTS code"
method: POST
path: "/api/v1/external/tariff-subscriptions"
tags: ["Tariff Subscriptions"]
---

# Subscribe to an HTS code

`POST /api/v1/external/tariff-subscriptions`

## Request

- Base URL: `https://api.wove.com`
- URL: `https://api.wove.com/api/v1/external/tariff-subscriptions`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/wove/apis/wove-external-api/docs/api/v1/external/tariff-subscriptions/post.ts> (a function with typed inputs; its response is checked against the schema)

Follow the duty treatment of an HTS code for a list of origins into one destination.
Requires the `tariff-subscriptions:write` scope.

## How delivery works

- Once a day every leaf tariff line × origin is re-evaluated. When anything differs from what
  was last delivered, the subscription's webhook receives **one** `tariff.changed` call listing
  every change. Nothing is sent when nothing changed.
- The treatment at creation (and for an origin added later) is the starting point and is not
  sent. Read it with `GET /tariff-subscriptions/{subscriptionId}`.
- A change is anything that decides the duty: the base rate, a Chapter 99 additional duty
  added, removed or re-rated, a Section 232 metal duty, an import prohibition, a claimable
  exclusion, or the tariff line itself being added, removed or re-described. A new HTS
  revision that restates the same duties is not a change.
- Delivery is at least once. A delivery that does not answer 2xx is retried within the day
  with everything since the last successful one. The webhook is disabled after 10 failures in
  a row spanning more than 24 hours.
- Every delivery carries `X-Wove-Delivery-Id`. The same evaluation sent twice carries the
  same id; answer 2xx to an id you have already processed and ignore it.
- A body is capped at 256 KB / 500 changes. Past that it sets `truncated: true` and
  `totalChanges`; read the rest from `GET /tariff-subscriptions/{subscriptionId}`.
- Each change carries its `changes` details. The full `previous` / `current` treatment is
  included only when the subscription sets `includeTreatments: true`.

## Verifying a delivery

`X-Wove-Signature` is the hex HMAC-SHA256 of the raw request body, keyed with the webhook
secret:

```js
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-wove-signature']))
```

## Example payload

```json
{
  "event": "tariff.changed",
  "timestamp": "2026-07-15T06:00:04.112Z",
  "data": {
    "subscriptionId": "1641343378340540417",
    "subject": { "hsCode": "22042120", "destinationCountry": "US", "originCountries": ["CA", "FR"] },
    "evaluatedFor": "2026-07-15",
    "changes": [
      {
        "kind": "scheduled",
        "effectiveDate": "2026-09-29",
        "lineCode": "2204.21.20.00",
        "originCountry": "CA",
        "changes": [
          { "type": "additional_duty.removed", "key": "9903.03.12|full" },
          { "type": "prohibition.added", "key": "section_338|2026-09-29||" }
        ],
        "previous": { "lineCode": "2204.21.20.00", "...": "full TariffTreatment, with includeTreatments" },
        "current": { "lineCode": "2204.21.20.00", "...": "full TariffTreatment, with includeTreatments" }
      }
    ]
  }
}
```

## Request body

- CreateTariffSubscriptionRequest
  - `hsCode` string, required: 6, 8 or 10 digit HTS code; dots are ignored. Expanded to its leaf tariff lines (at most 100).
  - `originCountries` string[], required: ISO 3166-1 alpha-2 codes, 1 to 50. Lines × origins may not exceed 1000.
  - `destinationCountry` 'US' | 'CA' | 'AU'
  - `notifyOn` 'effective' | 'scheduled' | 'both': - `effective`: when a change is in force on the evaluation date. - `scheduled`: when a future-dated change first appears within `lookaheadDays`, with its effective date; again if it is revised or withdrawn before that date. - `both`: both of the above.
  - `lookaheadDays` integer
  - `componentValues` object: Declared component values, same shape as the tariff lookup's `componentValues`.
  - `includeTreatments` boolean: Carry the full `previous` and `current` treatment on each change in `tariff.changed`. Off by default: each change then carries only its `changes` details, and `GET /tariff-subscriptions/{id}` has the full treatments.
  - `webhookId` string: An existing webhook to deliver to. Give this or `webhook`.
  - `webhook` object: Create an https webhook for this subscription. Its signing secret is returned once.
    - `url` string, uri, required
    - `name` string

## Response `200`

Subscription created

- object
  - `success` boolean
  - `data` object
    - `subscription` TariffSubscription
      - `id` string
      - `hsCode` string
      - `destinationCountry` 'US' | 'CA' | 'AU'
      - `originCountries` string[]
      - `notifyOn` 'effective' | 'scheduled' | 'both'
      - `lookaheadDays` integer
      - `componentValues` object, nullable
      - `includeTreatments` boolean
      - `webhookId` string
      - `isActive` boolean
      - `status` 'baselining' | 'active' | 'paused' | 'webhook_disabled': `baselining` until the starting treatment of every origin is recorded (usually seconds). `webhook_disabled` when the webhook was turned off after 10 failed deliveries in a row spanning more than 24 hours; re-enable it and everything since the last delivery is sent on the next evaluation.
      - `lastEvaluatedAt` string, date-time, nullable
      - `lastNotifiedAt` string, date-time, nullable
      - `nextEvaluationAt` string, date-time, nullable
      - `createdAt` string, date-time
      - `updatedAt` string, date-time
    - `webhookSecret` string: Present only when `webhook` was given, and only in this response.

## Other responses

- `400`: Invalid subject or limits exceeded
- `404`: Webhook not found
- `409`: The webhook already has a subscription to this HTS code and destination

---

[API](https://skmtc.dev/wove/apis/wove-external-api.md) - [All operations](https://skmtc.dev/wove/apis/wove-external-api/llms.txt) - [OpenAPI document](https://skmtc.dev/wove/apis/wove-external-api/revisions/37290e267e07?raw)
