Missing a trademark opposition deadline is irreversible. Once the window closes, a confusingly similar mark proceeds to registration, and your only recourse is a cancellation proceeding: 18 to 24 months of litigation, typically costing $15,000 to $100,000 or more before the Trademark Trial and Appeal Board (TTAB). The math is brutal. Monitoring costs a few API calls per day. Not monitoring costs a legal team.
The problem is that opposition windows are short, non-negotiable, and scattered across jurisdictions. Thirty days at the USPTO. Three months at EUIPO. Twelve to eighteen months for Madrid Protocol designations, depending on the designated office. If you're protecting a brand across multiple markets, you're tracking dozens of overlapping deadlines in different offices with different rules. That's not a calendar problem. That's a systems problem. The best way to solve it is to automate trademark monitoring with an API.
Why opposition deadlines matter
The opposition period is the last checkpoint before a trademark registers. After a trademark application passes examination, the office publishes it for opposition, opening a window during which any party can challenge the registration. If nobody opposes within the window, the mark registers. There is no extension, no grace period, no second chance.
The windows vary by office:
- USPTO: 30 days after publication in the Official Gazette
- EUIPO: 3 months from publication
- WIPO (Madrid Protocol): 12 to 18 months, depending on the designated office's national rules
Opposition Window Length by Office (Days)
The cost asymmetry is extreme. A single API-based monitoring watch costs fractions of a cent per check. Missing a deadline can mean accepting a competitor's confusingly similar mark in your market, or launching a cancellation proceeding that dwarfs the cost of the original filing by orders of magnitude.
Manual opposition period tracking works when you have one brand in one jurisdiction. It breaks down fast. A mid-size portfolio with marks in the US, EU, and Canada means monitoring three offices with three different publication schedules, three different opposition windows, and three different procedural rules. Multiply that by competitors filing in your Nice classes, and you have a problem that scales poorly with spreadsheets.
For a deeper look at what monitoring involves and why it matters for brand protection, see the complete guide to trademark monitoring. If you're new to trademark monitoring concepts, what trademark monitoring is and why it matters covers the fundamentals. Understanding opposition and cancellation proceedings is also useful context for what happens when you do file within the window.
How Signa's trademark monitoring API works
Signa's trademark monitoring API has three layers: watches, ingestion sync, and alert generation.
Watches define what you care about. You create a watch with a structured query DSL, and Signa evaluates every incoming trademark record against those criteria on each ingestion sync. Five watch types cover different monitoring strategies:
- similarity watches detect confusingly-similar new filings via semantic matching on mark text
- owner watches track a specific competitor's filing activity by owner ID
- class watches monitor entire Nice classes for new publications, optionally scoped by jurisdiction
- mark watches track a specific mark you own for status changes or renewals
- portfolio watches monitor a set of marks at once (your own portfolio)
For opposition deadline tracking, the critical trigger event is trademark.status_changed. This fires when a trademark moves through prosecution stages, including the transition to "published for opposition," which is exactly the moment you need to know about. The alternative triggers (trademark.created and trademark.updated) are useful for broader monitoring but generate more noise for this specific use case.
When a watch triggers, Signa generates an alert with a severity score (normal, high, or critical) and, for marks entering opposition windows, two fields: must_act_by (an ISO 8601 deadline timestamp) and opposition_window_status (one of open, closing_soon, critical, or closed). Severity escalates automatically as deadlines approach: normal when 14+ days remain, high at 7-14 days, critical under 7 days. No scheduling logic needed on your end.
For USPTO filings, Signa ingests data daily at 01:01 UTC (with retries at 05:01 and 13:01), meaning alerts typically arrive within 24 hours of the USPTO's daily file publication. For a 30-day opposition window, that gives you at least 29 days of lead time.
Signa currently monitors trademarks across 7+ production offices, including USPTO, EUIPO, WIPO, CIPO, NIPO, IP Australia, and IPOS. All watches run against all connected offices unless you scope them with a jurisdictions filter. Opposition windows are computed for 21 jurisdictions. For a fuller picture of how Signa structures trademark data across these offices, see the developer's guide to trademark data.
Setting up a similarity watch
The most common opposition monitoring pattern is a similarity watch on your brand name. This catches any new filing or status change for marks that are confusingly similar to yours, using semantic matching.
Here's how to create a similarity watch that monitors for marks similar to "NOVASTREAM" in software-related classes, triggering on status changes:
curl -X POST https://api.signa.so/v1/watches \
-H "Authorization: Bearer $SIGNA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: watch-novastream-similarity-001" \
-d '{
"name": "NOVASTREAM brand monitor",
"watch_type": "similarity",
"query": {
"version": "v1",
"q": "NOVASTREAM",
"filters": {
"niceClasses": [9, 42]
},
"trigger_events": ["trademark.status_changed"],
"score_threshold": 0.7
}
}'
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });
const watch = await signa.watches.create({
name: 'NOVASTREAM brand monitor',
watch_type: 'similarity',
query: {
version: 'v1',
q: 'NOVASTREAM',
filters: {
niceClasses: [9, 42],
},
trigger_events: ['trademark.status_changed'],
score_threshold: 0.7,
},
});
console.log(watch.id); // wat_8f3a1b2c4d5e6f7a
console.log(watch.status); // active
The response returns a watch with a wat_ prefixed ID. Key things to note:
score_thresholdcontrols matching sensitivity.0.7is a reasonable starting point for broad monitoring.0.85is tighter, better for production opposition workflows where you want fewer false positives.niceClasses(camelCase, as with all filter keys in the query DSL) scopes the watch to relevant classes. Class 9 covers software; Class 42 covers SaaS and technology services. Without class scoping, a similarity watch on a common term generates too much noise.trigger_events: ["trademark.status_changed"]is the right trigger for opposition tracking. It fires when a mark transitions to "published for opposition" status, not when someone first files an application. You could also includetrademark.createdto catch new filings early, before they reach publication.- One watch covers multiple offices. Unless you add a
jurisdictionsfilter, this watch runs against all 7+ production offices on every sync. A mark published at USPTO, EUIPO, or CIPO all trigger the same watch.
Monitoring a competitor's portfolio
Similarity watches protect your own brand. Owner and class watches let you track the broader competitive environment. If a competitor is aggressively expanding their trademark portfolio, you want to know before their marks register, not after.
An owner watch monitors all filing activity from a specific entity. You need the owner's Signa ID (prefixed with own_), which you can find through the owners search endpoint.
curl -X POST https://api.signa.so/v1/watches \
-H "Authorization: Bearer $SIGNA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: watch-competitor-apex-001" \
-d '{
"name": "Apex Digital filings",
"watch_type": "owner",
"query": {
"version": "v1",
"filters": {
"ownerId": "own_4c7d2e8f1a3b5c6d"
},
"trigger_events": ["trademark.status_changed"]
}
}'
const competitorWatch = await signa.watches.create({
name: 'Apex Digital filings',
watch_type: 'owner',
query: {
version: 'v1',
filters: {
ownerId: 'own_4c7d2e8f1a3b5c6d',
},
trigger_events: ['trademark.status_changed'],
},
});
For wider coverage, add a class watch to monitor entire Nice classes for new publications, regardless of who files them:
const classWatch = await signa.watches.create({
name: 'Class 9 software publications',
watch_type: 'class',
query: {
version: 'v1',
filters: {
niceClasses: [9],
jurisdictions: ['US', 'EU'],
},
trigger_events: ['trademark.created'],
},
});
You can list all active watches to verify your monitoring coverage:
curl https://api.signa.so/v1/watches \
-H "Authorization: Bearer $SIGNA_API_KEY"
const watches = await signa.watches.list();
for await (const watch of watches) {
console.log(`${watch.id} | ${watch.watch_type} | ${watch.name} | ${watch.status}`);
}
// wat_8f3a1b2c4d5e6f7a | similarity | NOVASTREAM brand monitor | active
// wat_9a2b3c4d5e6f7a8b | owner | Apex Digital filings | active
// wat_1b2c3d4e5f6a7b8c | class | Class 9 software publications | active
The combination of similarity, owner, and class watches gives you layered coverage. The similarity watch catches anything resembling your brand. The owner watch tracks specific competitors. The class watch catches everything else in your space. For more on building automated trademark search workflows, see how to automate trademark search with the Signa API.
Receiving alerts via webhooks
Watches generate alerts. Signa delivers them to your endpoint via webhooks, signed and retried automatically. Register a webhook to receive alert.created events:
curl -X POST https://api.signa.so/v1/webhooks \
-H "Authorization: Bearer $SIGNA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: webhook-opposition-alerts-001" \
-d '{
"url": "https://alerts.example.com/signa",
"description": "Opposition deadline alerts",
"enabled_events": ["alert.created"]
}'
The response includes a secret for verifying webhook signatures. Store it immediately. It's only shown once.
Each delivery is signed with HMAC-SHA256 per the Standard Webhooks specification. Verify the webhook-signature header against the request body using your stored secret. Reject requests where webhook-timestamp is more than 5 minutes old. Use the webhook-id header (which matches the alert's alt_* ID) as your idempotency key to handle retries.
The webhook payload for alert.created is self-contained. It includes the full alert, trademark snapshot, watch details, and deadline information inline. No follow-up API calls needed. Here's what your receiver gets:
{
"type": "alert.created",
"id": "alt_...",
"timestamp": "2026-06-12T01:14:09.412Z",
"data": {
"alert_id": "alt_...",
"watch_id": "wat_...",
"trademark_id": "tm_...",
"event_type": "trademark.status_changed",
"severity": "high",
"must_act_by": "2026-07-12T23:59:59-04:00",
"opposition_window_status": "open"
}
}
Each alert includes:
severityscored asnormal(14+ days remaining),high(7-14 days), orcritical(<7 days). Severity escalates automatically on each ingestion sync as the deadline approaches.must_act_byas an ISO 8601 timestamp (end-of-day in the office's local timezone), or null for jurisdictions where opposition windows aren't yet computedopposition_window_statusasopen,closing_soon,critical, orclosedtrademark_idreferencing the matched mark (tm_prefix)event_typeshowing which trigger fired (e.g.,trademark.status_changed)watch_idandwatch_namelinking back to the source watch
Signa retries failed deliveries with exponential backoff across 7 attempts (up to 6 hours). After 100 consecutive failures, the webhook auto-disables. You can re-enable it via PATCH /v1/webhooks/{id} and replay missed deliveries individually.
A practical triage workflow uses severity and opposition_window_status to route incoming alerts:
critical(under 7 days remaining): immediate legal review. These marks need a decision now.high(7-14 days remaining): flag for this week's IP counsel review.normalwithopposition_window_status: "open"(14+ days): track in the weekly digest.
Alerts are immutable. There are no acknowledge or dismiss operations in the API. Track processed alert IDs in your own system and dedupe by the webhook-id header on incoming deliveries.
Building a trademark opposition deadline dashboard
With webhooks delivering alerts in real-time, you can build a deadline dashboard that processes incoming alerts and maintains a live view of upcoming opposition deadlines.
Your webhook receiver stores each alert as it arrives. Here's a handler that extracts the deadline data and routes by severity:
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });
interface DeadlineEntry {
alertId: string;
trademarkId: string;
severity: string;
windowStatus: string;
mustActBy: Date;
daysRemaining: number;
}
const deadlines: Map<string, DeadlineEntry> = new Map();
function processAlert(payload: any): DeadlineEntry | null {
const { data } = payload;
if (!data.must_act_by || data.opposition_window_status === 'closed') return null;
const mustActBy = new Date(data.must_act_by);
const daysRemaining = Math.ceil((mustActBy.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
if (daysRemaining <= 0) return null;
const entry: DeadlineEntry = {
alertId: data.alert_id,
trademarkId: data.trademark_id,
severity: data.severity,
windowStatus: data.opposition_window_status,
mustActBy,
daysRemaining,
};
deadlines.set(data.alert_id, entry);
return entry;
}
function getUpcomingDeadlines(): DeadlineEntry[] {
return [...deadlines.values()]
.filter(d => d.daysRemaining > 0)
.sort((a, b) => a.daysRemaining - b.daysRemaining);
}
// Print the current dashboard
for (const d of getUpcomingDeadlines()) {
const label = d.severity === 'critical' ? 'URGENT' : d.severity === 'high' ? 'SOON' : 'OK';
console.log(
`[${label}] ${d.trademarkId} | ${d.daysRemaining} days left | ${d.windowStatus}`
);
}
// [URGENT] tm_3f7a1b2c | 4 days left | critical
// [SOON] tm_8d4e5f6a | 12 days left | closing_soon
// [OK] tm_1c9b2d3e | 47 days left | open
From here, integration points are straightforward:
- Calendar sync: Create calendar events for each deadline with 7-day and 3-day reminders.
- Slack notifications: Post to a
#trademark-alertschannel when newcriticalalerts arrive or whenopposition_window_statusisclosing_soon. - Legal review queue: Push urgent deadlines into your legal team's task management system with the trademark ID and a link to the Signa trademark detail endpoint for full prosecution history.
To enrich the dashboard with trademark details (mark text, owner, office), batch-fetch the trademarks referenced by your alerts:
const trademarkIds = getUpcomingDeadlines().map(d => d.trademarkId);
const marks = await signa.trademarks.batch({ ids: trademarkIds });
for (const tm of marks.data) {
console.log(`${tm.mark_text} (${tm.office_code}) | ${tm.status.stage}`);
}
Multi-jurisdiction considerations
A single brand can create opposition windows in multiple jurisdictions simultaneously. If you file through the Madrid Protocol (the international system that lets you file one application covering multiple countries), each designated office publishes the mark according to its own schedule and opens its own opposition window. A Madrid filing designating the US, EU, and Canada could create three separate opposition windows with three different closing dates.
Signa normalizes this complexity. The must_act_by field on each alert reflects the specific office's rules. A USPTO alert shows a 30-day window. An EUIPO alert on the same mark shows a 3-month window. Your code doesn't need jurisdiction-specific date arithmetic because Signa computes deadlines for 21 jurisdictions automatically. For offices where opposition window rules aren't yet wired, must_act_by and opposition_window_status return null. You can query the full set of supported rules via GET /v1/opposition-rules.
When setting urgency thresholds, the built-in opposition_window_status already accounts for the office's window length. closing_soon means 7-14 days remaining regardless of whether that's a 30-day window (USPTO) or a 3-month window (EUIPO). critical always means under 7 days. This eliminates the need to calculate percentages per jurisdiction.
Signa's watches run against all production offices by default. If you only operate in specific markets, scope your watches with the jurisdictions filter in the query DSL to reduce noise. But if you're monitoring a brand with international exposure, leave jurisdiction scoping off and let the alerts show you where your mark is being filed globally. Sometimes the most important signal is a mark filing in a jurisdiction you hadn't considered.
For more on monitoring across jurisdictions and enforcement strategies, see the guide to trademark monitoring and enforcement.
The full system in 50 lines
About 50 lines of TypeScript builds a complete opposition deadline monitoring system: similarity watches for your brand, owner watches for competitors, class watches for your market, webhook-driven alert delivery, and a deadline dashboard sorted by urgency. Signa handles the hard parts: multi-office ingestion, status normalization, semantic matching, opposition window computation across 21 jurisdictions, and automatic severity escalation as deadlines approach.
This tutorial covers monitoring and alerting. What happens after you identify a mark worth opposing (filing the opposition, evidence gathering, TTAB proceedings) is a legal process. Consult a trademark attorney for guidance specific to your situation.
Sign up for the Signa API beta at signa.so and start monitoring opposition windows in minutes.
