← All docs
Developers

Webhooks and Slack notifications

Get every finished audit pushed to Slack, Zapier or your own endpoint, the moment it is ready.

Overview

Notifications are part of the Pro plan ($29/month). Add up to 10 destinations in Settings, Integrations, then choose which events each one receives. Every destination has a Send test button, so you can check it works before a real audit finishes.

Slack

  1. In your Slack workspace, create an app (or add the Incoming Webhooks app).
  2. Turn on incoming webhooks for the app and add it to the channel you want.
  3. Copy the webhook URL. It starts with https://hooks.slack.com/.
  4. Paste it into Settings, Integrations, Slack, Add, then press Send test.

Zapier

  1. Create a Zap and choose Webhooks by Zapier as the trigger, with the event Catch Hook.
  2. Copy the URL Zapier gives you.
  3. Paste it into Settings, Integrations, Webhooks, Add, then press Send test.
  4. Back in Zapier, find the test request and build the rest of the Zap from its fields.

Events

EventWhen it is sent
audit.completedAudit complete. An audit finished and has a score.
audit.diffMonitored site changed. Every scheduled re-audit of a monitored site that has an earlier audit to compare against, whether or not anything changed.
audit.criticalCritical issues found. A finished audit has at least one failing critical check.
report.readyReport ready. A report you bought is ready to open.
fix.verifiedFix verified. An issue you marked as fixed was checked and is fixed.
audit.failedAudit failed. An audit could not finish.

Payload

Webhooks receive a JSON body like this one. Slack destinations get the same facts as a formatted message.

{
  "id": "sample",
  "event": "audit.completed",
  "created_at": "2026-11-04T09:00:00.000Z",
  "account": {
    "id": "sample"
  },
  "site": {
    "url": "https://example.com",
    "host": "example.com"
  },
  "audit": {
    "id": "sample",
    "url": "https://auditzap.io/audits/sample",
    "score": 78,
    "previous_score": 74,
    "delta": 4,
    "pages_audited": 10,
    "source": "dashboard",
    "completed_at": "2026-11-04T09:00:00.000Z"
  },
  "top_issues": [
    {
      "check": "Missing meta description",
      "category": "seo",
      "status": "fail"
    },
    {
      "check": "llms.txt present",
      "category": "ai_visibility",
      "status": "warn"
    }
  ],
  "summary": {
    "issues": 6,
    "passes": 34,
    "critical_failures": 1
  }
}

Headers

HeaderValue
Content-Typeapplication/json
User-AgentAuditZap-Webhooks/1
X-AuditZap-EventThe event name, for example audit.completed.
X-AuditZap-DeliveryA unique id for this delivery. Retries reuse it, so you can ignore repeats.
X-AuditZap-TimestampWhen we signed the request, in unix seconds.
X-AuditZap-Signaturev1= followed by the hex HMAC-SHA256 of "<timestamp>.<body>", keyed with your signing secret.

Verifying the signature

Each webhook destination has its own signing secret, shown in Settings. Compute v1= plus the hex HMAC-SHA256 of the timestamp, a full stop and the raw body, then compare it with the signature header in constant time. Reject requests whose timestamp is more than 5 minutes old.

Node:

import crypto from 'node:crypto'

// rawBody: the request body exactly as received, before any JSON parsing.
function verify(secret, headers, rawBody) {
  const timestamp = headers['x-auditzap-timestamp']
  const received = String(headers['x-auditzap-signature'] || '').replace(/^v1=/, '')
  // Reject anything older than 5 minutes to stop replays.
  if (Number(timestamp) + 300 < Date.now() / 1000) return false
  const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(received)
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

Python:

import hashlib, hmac, time

# raw_body: the request body exactly as received, decoded as UTF-8.
def verify(secret, headers, raw_body):
    timestamp = headers["X-AuditZap-Timestamp"]
    received = headers.get("X-AuditZap-Signature", "").removeprefix("v1=")
    # Reject anything older than 5 minutes to stop replays.
    if int(timestamp) + 300 < time.time():
        return False
    expected = hmac.new(secret.encode(), f"{timestamp}.{raw_body}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received)

Retries and failures

Only a 2xx response counts as delivered. We wait up to 10 seconds for a response and never follow redirects, so a 3xx counts as a failure. A failed delivery is tried 3 times in total, with an exponential backoff starting at 10 seconds.

After 10 failures in a row we pause the destination and email you. Fix the endpoint, then turn the destination back on in Settings.

Reporting abuse

If an AuditZap webhook is sending requests you did not ask for, tell us and we will stop it.