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.
What you’ll build
Signa webhook ─► Cloudflare Worker ─► Slack incoming webhook
(verify signature) (channel)
Two Slack-side requirements:
Two Signa-side requirements:
- A Signa API key (for creating the endpoint via the API).
- A Signa watch already created — see Watches.
Cloudflare Worker
Save as worker.js:
import { verifyWebhookSignature } from '@signa-so/sdk';
export default {
async fetch(req: Request, env: Env): Promise<Response> {
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
const body = await req.text();
const ok = verifyWebhookSignature(
{
'webhook-id': req.headers.get('webhook-id') ?? '',
'webhook-timestamp': req.headers.get('webhook-timestamp') ?? '',
'webhook-signature': req.headers.get('webhook-signature') ?? '',
},
body,
env.SIGNA_WEBHOOK_SECRET,
);
if (!ok) return new Response('invalid signature', { status: 401 });
const event = JSON.parse(body);
if (event.type !== 'alert.created') return new Response('ok', { status: 200 });
const alert = event.data;
const slackPayload = {
text: alertToSlackMessage(alert),
};
await fetch(env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(slackPayload),
});
return new Response('ok', { status: 200 });
},
};
interface Env {
SIGNA_WEBHOOK_SECRET: string;
SLACK_WEBHOOK_URL: string;
}
function alertToSlackMessage(alert: Record<string, unknown>): string {
const sev = alert.severity as string;
const emoji = sev === 'critical' ? ':rotating_light:' : sev === 'high' ? ':warning:' : ':bell:';
const window = alert.opposition_window_status
? ` (window: ${alert.opposition_window_status})`
: '';
return [
`${emoji} *Signa alert* — ${alert.event_type}${window}`,
`Watch: \`${alert.watch_id}\``,
`Trademark: \`${alert.trademark_id}\``,
alert.must_act_by ? `Action by: *${alert.must_act_by}*` : null,
]
.filter(Boolean)
.join('\n');
}
Customer code may want to enrich the message with a human-readable watch name via GET /v1/watches/{id}; the alert.created payload itself only carries the watch_id.
Set the env vars:
wrangler secret put SIGNA_WEBHOOK_SECRET
wrangler secret put SLACK_WEBHOOK_URL
wrangler deploy
Register the Worker URL with Signa:
import Signa from '@signa-so/sdk';
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });
const wh = await signa.webhooks.create({
url: 'https://signa-slack.<your-account>.workers.dev/',
description: 'Slack #legal-alerts',
enabled_events: ['alert.created'],
});
console.log('Set SIGNA_WEBHOOK_SECRET to:', wh.secret);
Copy wh.secret into the Worker as SIGNA_WEBHOOK_SECRET.
AWS Lambda + API Gateway alternative
import { verifyWebhookSignature } from '@signa-so/sdk';
import type { APIGatewayProxyHandlerV2 } from 'aws-lambda';
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
const body = event.body ?? '';
const ok = verifyWebhookSignature(
{
'webhook-id': event.headers['webhook-id'] ?? '',
'webhook-timestamp': event.headers['webhook-timestamp'] ?? '',
'webhook-signature': event.headers['webhook-signature'] ?? '',
},
body,
process.env.SIGNA_WEBHOOK_SECRET!,
);
if (!ok) return { statusCode: 401, body: 'invalid signature' };
const payload = JSON.parse(body);
if (payload.type === 'alert.created') {
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ text: format(payload.data) }),
});
}
return { statusCode: 200, body: 'ok' };
};
API Gateway: route a POST → Lambda. Set SIGNA_WEBHOOK_SECRET and
SLACK_WEBHOOK_URL as Lambda env vars.
Tips
- Slack rate-limits incoming webhooks. For high-volume watches, batch
with a 30s buffer or fan out to a topic and let multiple webhooks
consume.
- Slack truncates messages over 40k chars — keep formatting compact.
- For threading replies (e.g. “this watch matched 3 marks today”), use
the Slack Web API rather than incoming webhooks; the same verifier
pattern still applies.