Webhooks

Get notified the moment a waiver is signed. Add an endpoint from your dashboard under Settings → Integrations (admins only). We'll POST a JSON payload there for every matching event.

Events

The only event available today. More may be added later.

EventDescription
waiver.signedA waiver was signed and stored.

Payload

Every event is a POST request with a JSON body shaped like this. data varies by event type; this is waiver.signed's shape.

{
  "type": "waiver.signed",
  "data": {
    "signatureId": "77e81188-0bb5-452b-b902-8fc49fbbdefb",
    "waiverId": "d672eef4-24f2-4316-b6c7-c841ff179e74",
    "waiverTitle": "Guest Release Waiver",
    "customerName": "Jordan Reyes",
    "customerEmail": "jordan@example.com",
    "customerPhone": "555-0142",
    "submittedAt": "2026-08-24T18:04:00.191Z"
  }
}

Verifying the signature

Every request includes an X-Superwaiver-Signature header: the hex-encoded HMAC-SHA256 of the raw request body, signed with your webhook's secret (shown when you added it, and again anytime from its row in Settings → Integrations). Verify it against the raw body before parsing, not the re-serialized JSON, which may not byte-for-byte match what was signed.

Example

const crypto = require("crypto")

function isValidSignature(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex")
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
}

Example receiver

A minimal endpoint that verifies the signature and reads the event. Respond with any 2xx status to mark a delivery as successful; anything else (or a timeout) is recorded as failed.

Example

const http = require("http")
const crypto = require("crypto")

const WEBHOOK_SECRET = "whsec_..." // from Settings -> Integrations

function isValidSignature(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex")
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
}

http
  .createServer((req, res) => {
    if (req.method !== "POST") {
      res.writeHead(405).end("Method not allowed")
      return
    }

    let rawBody = ""
    req.on("data", (chunk) => (rawBody += chunk))
    req.on("end", () => {
      const signatureHeader = req.headers["x-superwaiver-signature"] || ""
      if (!isValidSignature(rawBody, signatureHeader, WEBHOOK_SECRET)) {
        res.writeHead(401).end("Invalid signature")
        return
      }

      const payload = JSON.parse(rawBody)
      if (payload.type === "waiver.signed") {
        // ...do something with payload.data.waiverTitle, payload.data.customerEmail, etc.
      }

      res.writeHead(200).end("OK")
    })
  })
  .listen(3000)

Delivery

Each webhook's row in Settings → Integrations shows when it last fired and whether that delivery succeeded. A failed delivery isn't retried automatically: if your endpoint was down, you'll need to catch up from your own records (e.g. the search API) rather than waiting for a redelivery.