Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.signa.so/llms.txt

Use this file to discover all available pages before exploring further.

Pick a delivery mode

You have three options for getting alerts out of Signa, in increasing order of operational complexity:
  1. Pull on a schedule. Cron job calls GET /v1/alerts and walks pages until it sees an id your store already has.
  2. Webhook. Sign up an endpoint via POST /v1/webhooks and let the dispatcher push to you.
  3. Webhook + reconciliation. Push for low-latency, but periodically poll POST /v1/alerts/lookup with the IDs you’ve persisted — confirms nothing was lost during a receiver outage.
Pure polling is fine for low-volume internal tools. For production external workflows, prefer webhook + reconciliation.

Deduplication

Alerts are immutable. The same alert.created event can arrive at your endpoint multiple times across retries and redeliveries. Use the webhook-id header (which equals the alert’s prefixed ID, alt_*) as your idempotency key.
const alertId = req.header('webhook-id')!;
if (await alreadyProcessed(alertId)) {
  return res.sendStatus(200); // ack but skip
}
await markProcessed(alertId);
await handle(JSON.parse(req.body));
return res.sendStatus(200);
The reverse problem — your store says you’ve processed an alert that you actually haven’t (e.g. because you crashed between markProcessed and handle) — is handled by recording state inside the same transaction as the side effect.

Polling pattern

let cursor: string | undefined;
while (true) {
  const page = await signa.alerts.list({ limit: 100, cursor });
  for (const alert of page.data) {
    if (await alreadyProcessed(alert.id)) {
      // Caught up — older alerts have already been processed.
      return;
    }
    await markProcessed(alert.id);
    await handle(alert);
  }
  if (!page.has_more) return;
  cursor = page.pagination.cursor!;
}

Reconciliation pattern

If you want defense-in-depth on top of webhooks, run a daily reconciler that asks the API “did you ever fire these IDs?” — useful when you suspect a webhook outage:
const recentIds = await myStore.alertsLast48h();
const confirmed = await signa.alerts.lookup(recentIds);
const seen = new Set(confirmed.map((a) => a.id));
for (const id of recentIds) {
  if (!seen.has(id)) reportPossibleLoss(id);
}
alerts.lookup() is org-scoped: alert IDs from other orgs (or malformed ones) are silently dropped. So the gap is real if it shows up.

Replay handling

If you intentionally call POST /v1/watches//replay to re-run a watch, you may receive duplicate alert.created deliveries for marks that previously fired. Your dedup-by-webhook-id logic keeps the data layer correct; if you also page customers, suppress notifications for IDs whose created_at is older than 1 hour.

Severity-based routing

const next = await signa.alerts.list({ severity: 'critical' });
for await (const a of next) routeToOnCallAttorney(a);
Keep two cron jobs running for any production workflow:
CadenceFilterWhat it does
1/minseverity=criticalAlert pages on-call.
1/hour(no filter)Catches normal/high for the daily review queue.

Sync run “fence” events

sync_run.searchable fires when an ingestion run is fully indexed and query-visible. If you maintain a downstream cache of trademark records, treat this as the “you can pull again” signal — it’s quieter than per-mark events and ideal for batch reconciliation.