# Webhook Tutorial

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

Source: https://docs.watsi.ai/en/webhooks/tutorial

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

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

```javascript 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)
```

### Expose it over HTTPS

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

```bash
ngrok http 4000
```

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

```bash
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"]
  }'
```

### Capture the signing secret

Store the returned signing secret in your environment before sending any real traffic to the handler.

```bash
export WATSI_WEBHOOK_SECRET='replace-with-your-secret'
node webhook-server.mjs
```

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

```text
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.
