TL;DR
- Register your filed HTS catalog with Tandom Monitoring. When CBP publishes a Customs Service Message (CSMS) that cites any code in the catalog, you get a webhook within roughly 90 seconds, with the affected codes resolved against the HTSUS hierarchy and the new rate broken out from the message body.
- CSMS messages drive every operational change inside an entry: deposit-rate flips on AD/CVD cases (CSMS 64384423 on Section 232 steel derivatives), entire layer replacements (CSMS 67834313 ending IEEPA collection in February 2026), and effective dates that move during the entry window (the April 2026 derivative-content reporting changes documented in CSMS 68253075). Missing one of these by even a day produces wrong duty.
- CBP's own GovDelivery feed mails every CSMS to every subscriber. A typical broker registered on the AD/CVD, ACE, Trade, and Cargo Security categories sees 40 to 80 messages a week, most of which touch products the broker does not file. Filtering to your registered catalog cuts the noise to under 5 messages a week for most importers.
- Subscribe through the Tandom Monitoring API (closed beta). Register a JSON catalog of HTS codes, AD/CVD case numbers, and country pairs; receive HMAC-signed webhooks on every match. Sample handlers in this guide route to Slack and open JIRA tickets.
The rest of this guide walks through the data model, the subscription shape, the webhook payload, and three handler patterns. Long-tail topics covered: tariff change webhook for brokers, Section 301 rate change notification, HTS code monitoring service, CBP CSMS subscription filter, tariff alert API, and Tandom Monitoring webhook.
Why CSMS, and why polling fails
CSMS is CBP's operational broadcast channel. When a Federal Register notice changes a duty rate, the FR notice establishes the legal authority, and a CSMS message tells the trade community how to file under it. The CSMS carries the new Chapter 99 codes, the effective date, the claim mechanics, and any transitional rules. Brokers do not file from FR text; they file from CSMS instructions.
The lag from FR publish to CSMS issuance ranges from same day to several weeks. Once a CSMS is out, the change is live. Three real examples drive home why a webhook beats a batch:
- February 2025 Section 232 reset. CSMS 64384423 ("UPDATED GUIDANCE: Import Duties on Imports of Steel and Steel Derivative Products") established the terminate-all-exemptions regime under Proclamation 10896, effective 12:01 a.m. EDT March 12, 2025. Brokers who saw the message that morning could quote correctly on shipments arriving the same week. Brokers on a weekly batch missed five business days of entries.
- February 2026 IEEPA shutdown. CSMS 67834313 ("Ending Collection of International Emergency Economic Powers Act Duties") instructed CBP to stop collecting IEEPA duties at 12:01 a.m. ET February 24, 2026, four days after the Supreme Court's decision in Learning Resources, Inc. v. Trump. Entries filed on February 24 with the IEEPA codes still attached were rejected. Operations teams that saw the CSMS published needed to flip every open template that referenced 9903.01.x, 9903.02.x, or 9903.96.01-.02 within hours.
- April 2026 derivative-content reporting. CSMS 68253075 consolidated the post-April-6 Section 232 reporting into the 9903.82 family for steel, aluminum, and copper. Pre-April-6 lines using 9903.80, 9903.81, 9903.85, and 9903.78 stopped clearing at the consolidation date. Importers shipping any of the ~1,200 derivative HTS lines in Chapters 73, 76, 82, 83, 84, 85, and 87 had to update their classification templates the day the change went live.
The polling alternative does not work in practice. Scraping content.govdelivery.com on a cron job catches new messages, but parsing the message body to figure out which HTS codes are affected, which case numbers are touched, and what the new rate is requires resolving cited codes at varying widths against the HTSUS hierarchy and pulling structured data out of natural-language CBP prose. That is the pipeline Tandom runs centrally so every subscriber inherits the same structured extraction. Browse the structured CSMS index in the Tandom AD/CVD catalog or read the CSMS triage guide for the manual workflow.
What kinds of changes you should alert on
A useful subscription discriminates between high-signal rate flips and routine procedural notices. Tandom classifies every CSMS into one of these event types, which the webhook payload carries on a top-level event_type field:
- rate.changed: a duty rate on a Chapter 99 code, an MFN code, or an AD/CVD case changes. Includes the new rate, the previous rate where the message states it, and the effective date.
- scope.changed: a Commerce scope determination expands or contracts the product scope of an AD/CVD order. Includes the case number, the affected HTS codes added or removed, and the determination's FR doc number.
- exclusion.granted / exclusion.expired: a Section 301 or Section 232 exclusion is granted, ends, or is extended. Includes the 9903.88.6x or 9903.85.x code, the importer-specific qualifier where applicable, and the bracketing dates.
- filing.requirements_changed: CBP changes how a layer is filed (new Chapter 99 code, consolidated 9903 family, new content-value reporting line). No rate change, but every open entry template referencing the old code needs an update.
- liquidation.instructions: Commerce issues liquidation instructions that lock in assessment rates for prior entries on a specific case. Affects refund or additional-payment positions, not the rate at entry.
- csms.superseded: a prior CSMS is replaced by a new one. The payload references the prior message id and the replacement id.
A finance team usually subscribes to rate.changed and exclusion.expired for cost-projection updates. An operations team subscribes to all six and routes them differently.
Register your HTS catalog
Subscriptions are scoped to one of three subjects: HTS code, AD/CVD case number, or origin country. The most common pattern is the per-importer HTS catalog: collect every 10-digit code the importer has filed in the last 24 months, deduplicate, and register them as a single subscription. Because Tandom expands cited codes against the HTSUS hierarchy at match time, a subscription on7616.99.51.90 also fires for any CSMS that cites the parent subheading 7616.99 or the parent heading 7616.
The closed-beta endpoints sit under /api/v1/monitoring/* and follow the GA shape. Authentication uses a Tandom API key in the Authorization: Bearer ... header. Beta access is gated; request through the calculator product team.
Create a subscription
POST https://tariffs.tandom.ai/api/v1/monitoring/subscriptions
Authorization: Bearer tnd_live_...
Content-Type: application/json
{
"name": "ACME steel + aluminum derivatives",
"webhook_url": "https://hooks.acme.com/tandom/csms",
"event_types": [
"rate.changed",
"scope.changed",
"exclusion.granted",
"exclusion.expired",
"filing.requirements_changed",
"csms.superseded"
],
"subjects": {
"hts_codes": [
"7318.15.80.66",
"7616.99.51.90",
"9404.90.20.60"
],
"adcvd_cases": [],
"origin_countries": ["CN", "MX", "VN"]
},
"match_strategy": "any"
}The response includes the subscription id, the signing_secret you use to verify webhooks, and a test_event_url you can hit to dispatch a synthetic delivery to your handler before any real traffic arrives.
Webhook payload shape
Every webhook is a JSON POST with a stable envelope. Codes resolved against the HTSUS hierarchy ride alongside the codes the message cited verbatim, so handlers do not have to expand cited codes themselves.
POST /tandom/csms HTTP/1.1
X-Tandom-Signature: sha256=8f3a...e12c
X-Tandom-Event-Id: 7c8b6d50-9b1e-4c0e-9f5a-1a4f5e8c12b7
Content-Type: application/json
{
"event_id": "7c8b6d50-9b1e-4c0e-9f5a-1a4f5e8c12b7",
"event_type": "filing.requirements_changed",
"occurred_at": "2026-04-06T13:02:11Z",
"subscription_id": "sub_018abc...",
"csms": {
"message_id": "68253075",
"title": "Section 232 Steel, Aluminum, and Copper: Updated Reporting under the 9903.82 Family",
"category": "Trade",
"sent_at": "2026-04-06T12:45:00Z",
"url": "https://compliance.tandom.ai/adcvd-catalog/csms/68253075"
},
"match": {
"hts_codes_cited_verbatim": ["7616.99"],
"hts_codes_resolved_in_subscription": ["7616.99.51.90"],
"adcvd_cases_cited": [],
"countries_cited": []
},
"change": {
"previous_chapter99_codes": ["9903.85.04", "9903.85.07"],
"new_chapter99_codes": ["9903.82.31", "9903.82.32"],
"rate_percent_before": 50,
"rate_percent_after": 50,
"effective_date": "2026-04-06"
}
}The change object is best-effort. CBP CSMS prose is sometimes ambiguous on whether a previous rate exists or what its value was. When Tandom cannot extract a structured comparison with high confidence, the field is null and the handler should treat the event as "open the message and read it manually." The csms.url field always points to the readable Tandom catalog page.
Verifying the signature
Every request carries an HMAC-SHA256 of the raw body in the X-Tandom-Signature header. Verify with a constant-time comparison before processing. The Node example:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyTandomSignature(
rawBody: Buffer,
header: string,
signingSecret: string,
): boolean {
const expected = "sha256=" + createHmac("sha256", signingSecret)
.update(rawBody)
.digest("hex");
const a = Buffer.from(header);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}Worked example: 14-code catalog
ACME imports a mix of steel and aluminum derivatives, finished metal articles, and bedding. Their filed catalog carries 14 distinct 10-digit HTS codes, three of which are highlighted here as the codes most likely to be hit by a Section 232 or Section 301 change. Every code below is verified against tariffs.tandom.ai/hts-catalog and the live Tandom duty engine.
Catalog (subset shown).
- 7318.15.80.66 (Cap screws of iron or steel, MFN 8.5%) — catalog. Subject to Section 232 (steel content), Section 301 List 3, and an advisory AD heading match against A-570-932 (Steel Threaded Rod from China).
- 7616.99.51.90 (Other articles of aluminum, MFN 2.5%) — catalog. Subject to Section 232 (aluminum derivatives, post June 4, 2025 doubling, smelt-and-cast country sensitive).
- 9404.90.20.60 (Pillows, cushions, and similar furnishings of cotton, with other fill, MFN 6%) — catalog. Section 301 List 4A exposure on China origin (
9903.88.15). - Eleven additional codes across Chapters 73, 76, 82, 83, 84, 85 (registered, omitted here for brevity).
The subscription. ACME POSTs the catalog to /api/v1/monitoring/subscriptions with event_types covering all six change kinds and origin_countries set to CN, MX, VN. The webhook URL points at a Slack posting endpoint hosted on their internal automation gateway.
The trigger. CBP publishes CSMS 68253075 on April 6, 2026, consolidating Section 232 reporting into the 9903.82 family for steel, aluminum, and copper derivatives. The message body cites HTS at the 4-digit and 6-digit level. ACME's catalog includes 7616.99.51.90; the message cites 7616.99. Tandom expands the cited subheading against the HTSUS hierarchy, finds the subscription match, classifies the event as filing.requirements_changed, and dispatches the webhook 47 seconds after CBP's GovDelivery publish.
The webhook ACME receives (abbreviated):
{
"event_id": "7c8b6d50-9b1e-4c0e-9f5a-1a4f5e8c12b7",
"event_type": "filing.requirements_changed",
"occurred_at": "2026-04-06T13:02:11Z",
"subscription_id": "sub_018abc...",
"csms": {
"message_id": "68253075",
"title": "Section 232 Steel, Aluminum, and Copper: Updated Reporting under the 9903.82 Family",
"category": "Trade",
"sent_at": "2026-04-06T12:45:00Z",
"url": "https://compliance.tandom.ai/adcvd-catalog/csms/68253075"
},
"match": {
"hts_codes_cited_verbatim": ["7318", "7616.99", "8708.10"],
"hts_codes_resolved_in_subscription": ["7616.99.51.90", "7318.15.80.66"],
"adcvd_cases_cited": [],
"countries_cited": []
},
"change": {
"previous_chapter99_codes": ["9903.85.04", "9903.81.94"],
"new_chapter99_codes": ["9903.82.31", "9903.82.51"],
"rate_percent_before": 50,
"rate_percent_after": 50,
"effective_date": "2026-04-06"
}
}What the handler does. ACME's handler verifies the signature, deduplicates on event_id, posts a Slack message to #ops-tariffs, and opens a JIRA ticket on the Brokerage board assigned to the lead broker. The ticket includes a deep-link back to the Tandom calculator with the affected HTS codes pre-loaded so the broker can confirm the new stack visually.
The 14-code catalog produces roughly 2 to 4 webhooks a month under normal conditions. April 2025 (Section 232 reset), May 2025 (UK deal), June 2025 (rate doubling), and February 2026 (IEEPA shutdown) each produced 1 to 3 webhooks per day for several days; outside those event windows the channel is quiet.
Handler patterns: Slack, JIRA, ACE
Three common handler patterns, in increasing order of operational depth.
Pattern 1: Slack notification
The simplest useful handler. The webhook arrives, the handler verifies the signature, formats a one-line summary, and posts to a Slack channel. Recommended for finance and pricing teams that want awareness, not action.
// Express + node-fetch handler
import express from "express";
import { verifyTandomSignature } from "./tandom";
const SIGNING_SECRET = process.env.TANDOM_SIGNING_SECRET!;
const SLACK_WEBHOOK = process.env.SLACK_WEBHOOK_URL!;
const app = express();
app.post("/tandom/csms",
express.raw({ type: "application/json" }),
async (req, res) => {
const sig = req.header("X-Tandom-Signature") ?? "";
if (!verifyTandomSignature(req.body, sig, SIGNING_SECRET)) {
return res.status(401).send("bad signature");
}
const event = JSON.parse(req.body.toString("utf8"));
const codes = event.match.hts_codes_resolved_in_subscription
.join(", ");
const text = [
`*${event.event_type}* — ${event.csms.title}`,
`HTS in catalog: ${codes}`,
`Effective: ${event.change?.effective_date ?? "see CSMS body"}`,
`<${event.csms.url}|Read CSMS ${event.csms.message_id}>`,
].join("\n");
await fetch(SLACK_WEBHOOK, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ text }),
});
res.status(200).send("ok");
}
);
app.listen(8080);Pattern 2: JIRA ticket on filing.requirements_changed
When the change requires a broker to update entry templates, a Slack message is not enough; a tracked ticket is. Filter the handler to filing.requirements_changed, rate.changed, and scope.changed; route the rest to a low-priority Slack channel.
const ACTIONABLE = new Set([
"filing.requirements_changed",
"rate.changed",
"scope.changed",
]);
if (ACTIONABLE.has(event.event_type)) {
await fetch("https://acme.atlassian.net/rest/api/3/issue", {
method: "POST",
headers: {
authorization: `Basic ${jiraBasicAuth}`,
"content-type": "application/json",
},
body: JSON.stringify({
fields: {
project: { key: "BRK" },
summary: `[CSMS ${event.csms.message_id}] ${event.csms.title}`,
issuetype: { name: "Task" },
priority: { name: "High" },
labels: ["tariff-change", event.event_type],
description: {
type: "doc",
version: 1,
content: [
paragraph(`Affected HTS in our catalog: ${codes}`),
paragraph(`Effective: ${event.change?.effective_date ?? "see CSMS"}`),
link(event.csms.url, `Read CSMS ${event.csms.message_id}`),
],
},
},
}),
});
}Pattern 3: ACE template re-validation
The deepest handler validates entry templates against the new state. When the webhook carries new_chapter99_codes, compare against open templates in the broker's filing system and either auto-update the templates or fail-closed (lock template until a human reviews). This is the pattern that prevented the most filing rejections during the IEEPA shutdown in February 2026: brokers who had this handler running flipped 9903.01.x and 9903.02.x out of every open template the moment CSMS 67834313 arrived, and started filing under Section 122 the same afternoon.
For ACE template re-validation specifically, the Tandom calculator engine is the natural reference. Each affected line can be re-priced through /api/duty/calculate with the entry date forward-shifted to the CSMS effective date; a difference between the old computed rate and the new one is the filing-update signal.
Common pitfalls
Eight mistakes that break a CSMS subscription pipeline:
Subscribing at the heading level only
A subscription on 7318 (heading) catches every CSMS that touches any 7318.xx product. That is fine for awareness but produces noise; the operations queue fills with messages on threaded rod, lag screws, and washers when the importer only files cap screws. Subscribe at the deepest classification you actually file (10 digits where possible) and let Tandom expand cited codes upward. The inverse direction is what you want: cited 6-digit subheading 7318.15 should match your 10-digit subscription, not the other way around.
Treating webhook delivery as exactly-once
Delivery is at-least-once. Network blips, your handler's 504, and CBP-issued reissues all produce duplicate deliveries. Persist processed event_id values for at least 7 days and skip duplicates. A handler that creates a JIRA ticket on every delivery without dedup creates 4 duplicate tickets the first time a retry storm hits.
Skipping signature verification
A webhook URL is by definition publicly reachable. Any attacker who guesses or scrapes the URL can post a forged event. Constant-time HMAC verification on every request is mandatory, not optional. The header is X-Tandom-Signature, the secret is per-subscription, and rotation is available through the API.
Polling /api/v1/monitoring/csms instead of webhooks
The pull endpoint exists for catch-up and dead-letter recovery, not as a primary delivery channel. A 5-minute poll cadence misses the median 90-second alert window; a 30-second cadence rate-limits before noon. Use webhooks for primary delivery, the pull endpoint as a backstop.
Treating csms.superseded as a no-op
CBP corrects CSMS messages by full reissue, not in-place edits. The pattern is: original message issues, errors are caught (rate cited wrong, effective date off by a day, an HTS code missed), a replacement message issues with a new CSMS id. Tandom emits csms.superseded on the prior id and csms.published on the replacement. Handlers must invalidate the prior alert and reprocess the replacement; treating the supersede as a no-op leaves the prior (wrong) state in the operations channel.
Filtering out csms.superseded events
The opposite mistake of the above. A handler that filters to "high signal" event types and drops supersedes leaves stale tickets and stale Slack messages in place. Always process the supersede, even if the only action is to comment "superseded by CSMS <new id>" on the prior ticket.
Hardcoding the IEEPA codes
A handler that hardcodes 9903.01.x or 9903.02.x as "current Section 122 / IEEPA" stopped working at 12:01 a.m. ET February 24, 2026 when CSMS 67834313 ended IEEPA collection. The pattern that survives is reading the new Chapter 99 codes off the webhook payload, not from a constant in the handler.
Not registering AD/CVD case numbers separately
Some CSMS messages cite a case number without citing any HTS code (liquidation instructions on a single respondent, for example). A subscription that only lists HTS codes misses these. If the importer is exposed to a specific AD or CVD case (an order's product scope reaches the importer's product, even at heading-level), register the case number directly in adcvd_cases on the subscription.
Glossary
- CSMS
- Customs Service Message. CBP's operational broadcast channel to the trade community. Each message has an integer id (e.g., 64384423) and a category (AD/CVD, ACE, Trade, Cargo Security, Other). Authoritative source: content.govdelivery.com bulletin feed; mirrored in the Tandom AD/CVD catalog with structured extraction.
- GovDelivery
- The mailing-list service CBP uses to publish CSMS. Mass email; no per-subscriber filtering. Tandom Monitoring polls GovDelivery and adds the filtering and classification.
- Webhook
- An HTTP POST from Tandom to a URL the subscriber registers, fired the moment a matching event occurs. Signed with HMAC-SHA256 in X-Tandom-Signature. At-least-once delivery with exponential backoff retry.
- Subscription
- A registered set of subjects (HTS codes, AD/CVD cases, countries) plus event types plus a webhook URL. Identified by sub_... prefix. Owns its signing secret and dead-letter queue.
- Event id
- A UUID that uniquely identifies a single event delivery. Stable across retries. Handlers must dedupe on this id to avoid double-processing.
- Match strategy
- "any" fires the webhook when any registered subject is cited. "all" requires every registered subject to be cited (rare; useful for narrow research subscriptions). Default is "any".
- Code resolution
- The process of expanding a cited HTS code (which may be 4, 6, 8, or 10 digits) against the HTSUS hierarchy to find subscription matches at the deepest level the subscriber registered.
- Dead-letter queue
- A per-subscription queue of events whose webhook delivery failed after 24 hours of retries. Readable through GET /api/v1/monitoring/subscriptions/{id}/dead-letters.
- 9903.82 family
- The post-April-6, 2026 consolidated Chapter 99 family for Section 232 steel, aluminum, and copper reporting. Replaced 9903.80, 9903.81, 9903.85, and 9903.78 codes on entries from April 6, 2026 forward (per CSMS 68253075 and surrounding messages).
- Chapter 99
- The HTSUS chapter that holds temporary trade-remedy provisions: Sections 232, 301, 201 safeguards, IEEPA (now expired), Section 122 (current). The 9903 four- digit prefix is reported as a secondary classification on the entry line.
- Liquidation instructions
- CSMS-issued directions from Commerce to CBP that lock in final assessment rates for prior entries on a specific AD or CVD case. Affect refund or additional-payment positions for entries already filed, not the rate at entry.
- Signing secret
- The per-subscription shared secret used to sign every webhook with HMAC-SHA256. Returned at subscription creation, rotatable through the API. Old secret remains valid for 24 hours after rotation to allow handler cutover.
- Tandom catalog
- The browsable mirror of the underlying primary sources. HTS codes at tariffs.tandom.ai/hts-catalog, AD/CVD orders and CSMS messages at compliance.tandom.ai/adcvd-catalog.
FAQ
High-intent questions brokers and importer compliance teams ask before adopting a CSMS alert pipeline.