This walkthrough shows one practical local-development flow: stand up a handler, expose it with a tunnel, register a subscription, then verify the signed delivery you receive.
Steps
- 1
Start a local handler
Create a small HTTP endpoint that stores the raw request body and verifies the
X-Watsi-Signatureheader before parsing JSON.webhook-server.mjsimport crypto from 'crypto' import http from 'http' const secret = process.env.WATSI_WEBHOOK_SECRET ?? '' function verifySignature(rawBody, signature) { if (!signature) return false const computed = crypto.createHmac('sha256', secret).update(rawBody).digest('hex') const expectedBuf = Buffer.from(String(signature), 'utf8') const computedBuf = Buffer.from(computed, 'utf8') return ( expectedBuf.length === computedBuf.length && crypto.timingSafeEqual(expectedBuf, computedBuf) ) } http .createServer((req, res) => { if (req.method !== 'POST' || req.url !== '/webhooks/watsi') { res.writeHead(404) res.end() return } const chunks = [] req.on('data', (chunk) => chunks.push(chunk)) req.on('end', () => { const rawBody = Buffer.concat(chunks) const signature = req.headers['x-watsi-signature'] if (!verifySignature(rawBody, signature)) { res.writeHead(401) res.end('Invalid signature') return } const event = JSON.parse(rawBody.toString('utf8')) console.log('verified event', event.type, event.id) res.writeHead(200) res.end('OK') }) }) .listen(4000) - 2
Expose it over HTTPS
Use a tunnelling tool such as ngrok so watsi can reach your local machine.
ngrok http 4000 - 3
Register a subscription
Create a subscription that points to the HTTPS URL from your tunnel. The exact REST surface may evolve, but the integration contract is: target URL, event list, and a signing secret stored by your application.
curl -X POST https://api.watsi.ai/api/v1/webhooks \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://YOUR_TUNNEL_HOST/webhooks/watsi", "events": ["message.received", "message.status"] }' - 4
Capture the signing secret
Store the returned signing secret in your environment before sending any real traffic to the handler.
export WATSI_WEBHOOK_SECRET='replace-with-your-secret' node webhook-server.mjs - 5
Trigger a test delivery
Send a real inbound message or use your subscription-management surface to trigger a test event. Your logs should show a verified event type and event id.
verified event message.received evt_01HXYZ123456789
Production checklist
Before you go live
- Respond quickly with
2xxand do heavy work asynchronously. - Use
event.idorX-Watsi-Delivery-Idfor deduplication. - Persist logs with event type, delivery id, and workspace id.
- Treat unknown future event types as non-fatal.
- Keep the signing secret in a secret manager, not in source code.