> ## Documentation Index
> Fetch the complete documentation index at: https://docs.infraudit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscribe to Webhook Events from InfraAudit

> Register a webhook endpoint, verify HMAC signatures on delivery, and handle drift, alert, and vulnerability events in a Node.js receiver.

This example creates a webhook subscription, verifies the signature on each incoming delivery, and handles `drift.detected`, `alert.created`, and `vulnerability.found` events in a Node.js receiver.

## Prerequisites

* A publicly accessible HTTP endpoint (use [ngrok](https://ngrok.com) for local testing)

```bash theme={null}
export TOKEN="eyJhbGciOi..."
export BASE_URL="https://api.infraaudit.dev"
export RECEIVER_URL="https://receiver.example.com/infraudit"
```

## Step 1: Create the webhook

```bash theme={null}
WEBHOOK=$(curl -s -X POST "$BASE_URL/api/v1/webhooks" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"Production receiver\",
    \"url\": \"$RECEIVER_URL\",
    \"events\": [\"drift.detected\", \"alert.created\", \"vulnerability.found\"],
    \"enabled\": true
  }")

echo $WEBHOOK | jq .

WEBHOOK_ID=$(echo $WEBHOOK | jq -r '.id')
WEBHOOK_SECRET=$(echo $WEBHOOK | jq -r '.secret')

echo "Secret (save this now): $WEBHOOK_SECRET"
```

<Warning>
  The `secret` is returned only once. Store it immediately in your secrets manager or deployment environment — you cannot retrieve it again.
</Warning>

## Step 2: Send a test delivery

```bash theme={null}
curl -s -X POST "$BASE_URL/api/v1/webhooks/$WEBHOOK_ID/test" \
  -H "Authorization: Bearer $TOKEN" | jq .
```

Your endpoint should receive a `ping` event.

## Step 3: Implement the receiver (Node.js)

```javascript theme={null}
// receiver.js
const express = require('express')
const crypto = require('crypto')

const app = express()
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET

function verifySignature(body, signature) {
  if (!signature) return false
  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET)
  hmac.update(body, 'utf8')
  const expected = 'sha256=' + hmac.digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
}

app.post('/infraudit', express.raw({ type: '*/*' }), (req, res) => {
  const signature = req.headers['x-infraaudit-signature']

  if (!verifySignature(req.body, signature)) {
    console.error('Invalid signature')
    return res.status(401).send('Unauthorized')
  }

  const { event, delivery_id, data } = JSON.parse(req.body.toString())
  console.log(`Received ${event} (delivery: ${delivery_id})`)

  switch (event) {
    case 'drift.detected':
      console.log(`Drift on ${data.resource_name}: ${data.summary} (${data.severity})`)
      break
    case 'alert.created':
      console.log(`Alert: ${data.title} (${data.severity})`)
      break
    case 'vulnerability.found':
      console.log(`CVE ${data.cve_id} on ${data.resource_name}`)
      break
    case 'ping':
      console.log('Ping received — endpoint verified')
      break
  }

  res.sendStatus(200)
})

app.listen(3000, () => console.log('Receiver running on :3000'))
```

## Step 4: Check delivery history

```bash theme={null}
curl -s "$BASE_URL/api/v1/webhooks/$WEBHOOK_ID/deliveries" \
  -H "Authorization: Bearer $TOKEN" \
  | jq '.data[] | {event: .event_type, status, response_code}'
```

## Step 5: Local testing with ngrok

If your receiver runs locally:

```bash theme={null}
ngrok http 3000
# Forwarding: https://abc123.ngrok.io -> http://localhost:3000
```

Use the ngrok URL as `RECEIVER_URL` when creating the webhook.

<Tip>
  For more on signature verification and retry behavior, see [Signature Verification](/api/webhooks/signing) and [Retries & Delivery](/api/webhooks/retries).
</Tip>
