Skip to main content
Set up real-time monitoring for trademark filings and status changes with webhooks.

Overview

Instead of polling our API repeatedly, webhooks notify your application when events occur:
  • New trademark filings matching your criteria
  • Status changes on monitored trademarks
  • Similar marks detected
  • Opposition proceedings started
  • Registrations granted or cancelled
Benefits:
  • Real-time notifications (no polling required)
  • No credit consumption (monitors are subscription-based)
  • Reliable event delivery with automatic retries
  • Secure with HMAC signature verification

Creating a Monitor

POST /v1/monitors
Example:
curl -X POST https://api.signa.so/v1/monitors \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Monitor tech competitors",
    "description": "Track new filings in software/SaaS space",
    "watches": [
      {
        "type": "query",
        "value": "tech",
        "search_type": "fuzzy"
      },
      {
        "type": "class",
        "value": [9, 42]
      }
    ],
    "offices": ["USPTO", "EUIPO"],
    "webhook_url": "https://myapp.com/webhooks/trademarks",
    "webhook_secret": "whsec_abc123xyz...",
    "webhook_events": [
      "new_filing",
      "status_change",
      "similar_mark_detected"
    ]
  }'
Watch Types:
TypeDescriptionExample
queryText query with search type{"type": "query", "value": "apple", "search_type": "exact"}
classNice classification classes{"type": "class", "value": [9, 35]}
ownerOwner name{"type": "owner", "value": "Apple Inc"}
trademark_idSpecific trademark{"type": "trademark_id", "value": "tm_us_1234567"}

Webhook Events

EventTriggered When
new_filingNew trademark filed matching your criteria
status_changeTrademark status changes (e.g., PENDING → REGISTERED)
similar_mark_detectedSimilar mark filed (fuzzy match)
trademark_publishedTrademark published for opposition
trademark_registeredTrademark officially registered
trademark_opposedOpposition filed against trademark
trademark_cancelledTrademark cancelled or abandoned

Webhook Payload

When an event occurs, we POST to your webhook_url:
{
  "event": "new_filing",
  "monitor_id": "mon_abc123",
  "monitor_name": "Monitor tech competitors",
  "triggered_at": "2025-10-15T14:30:00Z",
  "trademark": {
    "id": "tm_us_9999999",
    "office": {
      "code": "USPTO",
      "name": "United States Patent and Trademark Office"
    },
    "mark": {
      "text": "TECHFLOW PRO",
      "type": "word"
    },
    "status": {
      "unified_status": "live_pending",
      "unified_status_code": "LP"
    },
    "dates": {
      "filing_date": "2025-10-14"
    },
    "classifications": {
      "nice_classes": [9, 42]
    },
    "owners": [
      {
        "name": "TechFlow Inc.",
        "country": "US"
      }
    ]
  },
  "match_reason": "Fuzzy match with similarity score 0.87",
  "urls": {
    "trademark": "https://api.signa.so/v1/trademarks/tm_us_9999999",
    "office": "https://tsdr.uspto.gov/#caseNumber=99123456"
  }
}

Receiving Webhooks

Basic Handler

const express = require('express');
const crypto = require('crypto');

app.post('/webhooks/trademarks', express.json(), async (req, res) => {
  // 1. Verify signature
  const signature = req.headers['x-trademark-signature'];
  if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Process event
  const { event, trademark } = req.body;

  console.log(`Received ${event} for ${trademark.mark.text}`);

  // 3. Respond quickly (< 5 seconds)
  res.status(200).send('OK');

  // 4. Process async
  processWebhookAsync(req.body);
});

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Signature Verification

We sign all webhooks with HMAC-SHA256:
X-Trademark-Signature: <hmac_sha256_hex>
Always verify signatures to prevent spoofed webhooks.

Retry Logic

If your endpoint fails to respond with 2xx:
  • Attempt 1: Immediately
  • Attempt 2: After 1 minute
  • Attempt 3: After 5 minutes
  • Attempt 4: After 30 minutes
  • Attempt 5: After 2 hours
After 5 failures, the webhook is marked as failed and monitoring is paused.

Best Practices

Always respond with 200 within 5 seconds. Process data async:
app.post('/webhooks', (req, res) => {
  // Respond immediately
  res.status(200).send('OK');

  // Process in background
  queue.add('process-webhook', req.body);
});
Handle duplicate events gracefully:
const processedEvents = new Set();

async function processWebhook(data) {
  const eventId = `${data.event}_${data.trademark.id}_${data.triggered_at}`;

  if (processedEvents.has(eventId)) {
    console.log('Duplicate event, skipping');
    return;
  }

  processedEvents.add(eventId);
  // Process event...
}
Never skip signature verification:
// Bad
app.post('/webhooks', (req, res) => {
  processEvent(req.body); // No verification!
});

// Good
app.post('/webhooks', (req, res) => {
  if (!verifySignature(req.body, req.headers['x-trademark-signature'])) {
    return res.status(401).send('Invalid');
  }
  processEvent(req.body);
});
Track webhook delivery success:
const stats = {
  received: 0,
  processed: 0,
  failed: 0
};

app.post('/webhooks', async (req, res) => {
  stats.received++;

  try {
    await processEvent(req.body);
    stats.processed++;
    res.status(200).send('OK');
  } catch (error) {
    stats.failed++;
    // Still respond 200 if recoverable
    res.status(200).send('Accepted');
  }
});

Managing Monitors

List Monitors

GET /v1/monitors

Get Monitor Details

GET /v1/monitors/{monitor_id}

Update Monitor

PATCH /v1/monitors/{monitor_id}
{
  "name": "Updated name",
  "webhook_events": ["new_filing"]
}

Delete Monitor

DELETE /v1/monitors/{monitor_id}

Testing Webhooks

Test Event

Trigger a test webhook:
POST /v1/monitors/{monitor_id}/test
We’ll send a test payload to your webhook URL.

Local Testing

Use tools like ngrok for local development:
# Terminal 1: Start your server
node server.js

# Terminal 2: Expose localhost
ngrok http 3000

# Use ngrok URL as webhook_url
https://abc123.ngrok.io/webhooks/trademarks

Webhook Security

Security Best Practices:
  1. Always verify signatures
  2. Use HTTPS only (we reject HTTP URLs)
  3. Keep webhook secrets secure
  4. Rotate secrets periodically
  5. Validate payload structure
  6. Rate limit your endpoint
  7. Log all webhook attempts

Pricing

Monitors are priced separately from API credits:
PlanIncluded MonitorsAdditional Cost
Starter2$10/month each
Growth10$5/month each
Pro50$2/month each
EnterpriseUnlimitedIncluded
Webhook deliveries are free (no credit cost).

FAQ

We’ll retry for up to 2 hours. After 5 failures, monitoring is paused. Fix your endpoint and resume in the dashboard.
Yes! Create separate monitors with different webhook URLs.
Typically within seconds of the event occurring. During high volume, up to 1 minute.
Yes, use the webhook_events array when creating the monitor.