Skip to content
watsi

Webhooks

Webhook Tutorial

Register a watsi webhook, expose a local endpoint, receive a test event, and verify the signature.

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. 1

    Start a local handler

    Create a small HTTP endpoint that stores the raw request body and verifies the X-Watsi-Signature header before parsing JSON.

    webhook-server.mjs
    import 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. 2

    Expose it over HTTPS

    Use a tunnelling tool such as ngrok so watsi can reach your local machine.

    ngrok http 4000
  3. 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. 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. 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 2xx and do heavy work asynchronously.
  • Use event.id or X-Watsi-Delivery-Id for 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.