Developer Documentation

Webhook Documentation

Receive signed waiver events in real time, verify every payload with HMAC SHA-256, and include guardian/minor details or video waiver completion evidence where relevant.

Events

Signed waivers, template changes, and record updates.

Signed Requests

HMAC SHA-256 over the raw request body.

Video Evidence

Completion summaries for video waiver submissions.

JSON Payloads

Stable top-level structure with nullable optional sections.

Overview

Webhooks allow you to receive HTTP POST notifications when specific events occur. Perfect for integrating waiver submissions, guardian/minor records, and video waiver completion evidence into your CRM, access control, marketing automation, or custom applications in real-time.

Trigger CRM or marketing automation when a waiver is submitted
Sync signed waiver records into an external operations system
Grant or update access-control status after a completed waiver
Capture video safety waiver completion evidence for audit workflows

Available Events

Subscribe to the events your integration needs.

waiver.submitted

Fired when a waiver is signed and submitted

Active
waiver.verified

Fired when a submitted waiver is successfully email verified

Active
waiver.expiring_soon

Fired when an unverified waiver is approaching its verification expiry window

Active
waiver.expired

Fired when a waiver is marked expired by the expiration cron

Active
waiver.updated

Fired when an existing waiver is modified (status, notes, compliance)

Active
waiver.deleted

Fired when a waiver is deleted (GDPR compliance)

Active
template.created

Fired when a waiver template is created

Active
template.updated

Fired when a waiver template is modified

Active
template.archived

Fired when a waiver template is archived instead of hard deleted, usually because signed waivers reference it

Active
template.deleted

Fired when an unused waiver template is permanently deleted

Active

Webhook Headers

Every webhook request includes these headers.

X-Webhook-SignatureHMAC SHA-256 signature (computed on raw body string)
X-Webhook-EventEvent type (e.g., waiver.submitted)
X-Webhook-TimestampISO 8601 timestamp
X-Webhook-DeliveryUnique delivery identifier. Use this for idempotent processing when present.
Content-Typeapplication/json

Signature Verification

The signature is computed on the raw JSON request body string, not a parsed or re-serialized object. You must capture the raw body before parsing to verify correctly.

// Node.js/Express example
const crypto = require('crypto');

function verifyWebhook(rawBodyString, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBodyString)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// Express.js - capture raw body:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const rawBody = req.body.toString('utf8');

  if (!verifyWebhook(rawBody, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(rawBody);
  // Process webhook...
});

Payload Examples

The top-level keys stay consistent. Optional sections such as guardianInfo, participants, and videoCompletion are populated only when relevant.

Adult Personal Waiver

When an adult signs a waiver for themselves. signerInfo contains the participant details. guardianInfo is null, participants is empty, and videoCompletion is null for non-video templates.

{
  "event": "waiver.submitted",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "organizationId": "org_abc123",
  "data": {
    "waiverId": "waiver_xyz789",
    "templateId": "template_123",
    "templateName": "Standard Liability Waiver",
    "templateType": "text",
    "isGuardianSigning": false,
    "numberOfMinors": 0,
    "signerInfo": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "[email protected]",
      "phone": "0412 345 678",
      "dateOfBirth": "1990-05-15"
    },
    "guardianInfo": null,
    "participants": [],
    "videoCompletion": null,
    "formData": {
      "fullName": "John Doe",
      "email": "[email protected]",
      "phone": "0412 345 678",
      "dateOfBirth": "1990-05-15",
      "emergencyContactName": "Jane Doe",
      "emergencyContactPhone": "0423 456 789",
      "confirmRead": true,
      "confirmAge": true,
      "confirmVoluntary": true,
      "signature": "data:image/png;base64,..."
    },
    "compliance": {
      "certificateId": "WW-123456",
      "signedAt": "2025-01-15T10:30:00.000Z",
      "ipAddress": "203.45.67.89",
      "userAgent": "Mozilla/5.0...",
      "signatureMethod": "electronic",
      "devicePlatform": "desktop"
    },
    "organization": {
      "id": "org_abc123",
      "name": "Adventure Sports Co"
    },
    "status": "completed"
  }
}

Guardian/Minor Waiver

When a parent or guardian signs on behalf of one or more minors. signerInfo contains the guardian details, guardianInfo is populated, and participants contains the minor records built from participants[] or participantInfo.

{
  "event": "waiver.submitted",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "organizationId": "org_abc123",
  "data": {
    "waiverId": "waiver_xyz789",
    "templateId": "template_123",
    "templateName": "Youth Activity Waiver",
    "templateType": "text",
    "isGuardianSigning": true,
    "numberOfMinors": 2,
    "signerInfo": {
      "firstName": "Sarah",
      "lastName": "Smith",
      "email": "[email protected]",
      "phone": "0412 345 678",
      "dateOfBirth": null
    },
    "guardianInfo": {
      "fullName": "Sarah Smith",
      "email": "[email protected]",
      "phone": "0412 345 678",
      "relationship": "Parent"
    },
    "participants": [
      {
        "fullName": "Emily Smith",
        "dateOfBirth": "2015-03-20",
        "customFields": {
          "allergies": "None",
          "medicalConditions": "Asthma - mild"
        }
      },
      {
        "fullName": "James Smith",
        "dateOfBirth": "2012-08-10",
        "customFields": {
          "allergies": "Peanuts",
          "medicalConditions": "None"
        }
      }
    ],
    "videoCompletion": null,
    "formData": {
      "guardianFullName": "Sarah Smith",
      "guardianEmail": "[email protected]",
      "guardianPhone": "0412 345 678",
      "guardianRelationship": "Parent",
      "emergencyContactName": "Michael Smith",
      "emergencyContactPhone": "0423 456 789",
      "confirmRead": true,
      "confirmAge": true,
      "confirmVoluntary": true,
      "confirmGuardianship": true,
      "confirmAuthority": true,
      "confirmLegalResponsibility": true,
      "guardianSignature": "data:image/png;base64,..."
    },
    "compliance": {
      "certificateId": "WW-789012",
      "signedAt": "2025-01-15T10:30:00.000Z",
      "ipAddress": "203.45.67.89",
      "userAgent": "Mozilla/5.0...",
      "signatureMethod": "electronic",
      "devicePlatform": "mobile"
    },
    "organization": {
      "id": "org_abc123",
      "name": "Adventure Sports Co"
    },
    "status": "completed"
  }
}

Video Waiver

When a video waiver is submitted. videoCompletion is included in the waiver.submitted webhook payload as a submission-time summary generated from the template videos, including watched percentage and checkpoint pass counts. This example shows an adult signing; guardian-mode video waivers still populate guardianInfo and participants the same way as text waivers.

{
  "event": "waiver.submitted",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "organizationId": "org_abc123",
  "data": {
    "waiverId": "waiver_xyz789",
    "templateId": "template_456",
    "templateName": "Safety Training Waiver",
    "templateType": "video",
    "isGuardianSigning": false,
    "numberOfMinors": 0,
    "signerInfo": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "[email protected]",
      "phone": "0412 345 678",
      "dateOfBirth": "1990-05-15"
    },
    "guardianInfo": null,
    "participants": [],
    "videoCompletion": {
      "completedAt": "2025-01-15T10:30:00.000Z",
      "videosWatched": [
        {
          "videoId": "video_001",
          "name": "Safety Orientation",
          "watchedPercentage": 100,
          "checkpointsPassed": 3,
          "checkpointsTotal": 3
        }
      ],
      "passed": true
    },
    "formData": {
      "fullName": "John Doe",
      "email": "[email protected]",
      "confirmRead": true,
      "confirmAge": true,
      "confirmVoluntary": true,
      "confirmVideoWatched": true,
      "signature": "data:image/png;base64,..."
    },
    "compliance": {
      "certificateId": "WW-345678",
      "signedAt": "2025-01-15T10:30:00.000Z",
      "ipAddress": "203.45.67.89",
      "userAgent": "Mozilla/5.0...",
      "signatureMethod": "electronic",
      "devicePlatform": "desktop"
    },
    "organization": {
      "id": "org_abc123",
      "name": "Adventure Sports Co"
    },
    "status": "completed"
  }
}

Retries and Idempotency

Build receivers that are safe when deliveries are retried or downstream systems are slow.

Delivery retries

  • Rock Solid Waivers treats any 2xx response as accepted.
  • Non-2xx responses, network errors, and timeouts may be retried.
  • Retry state is stored durably and processed by a cron-backed worker, so retries can continue after deploys or restarts.
  • Receivers should finish quickly and move slow downstream work into a queue.
  • Return 2xx only after the event has been safely accepted or stored.

Idempotent processing

  • Use X-Webhook-Delivery when present, or combine event, data.waiverId, and timestamp as a fallback idempotency key.
  • Store processed delivery IDs before performing side effects such as door access updates, CRM writes, or email sends.
  • If a duplicate delivery arrives, return 200 after confirming the earlier delivery was already accepted.
Design receivers to respond within 10 seconds. Do not block the webhook response on slow third-party APIs.

Integration Notes

Use these rules when building your webhook receiver.

Verify the signature before parsing or trusting the payload.

Use the raw request body string for HMAC verification. Re-stringifying parsed JSON can change spacing or key order and break verification.

Treat webhook handlers as idempotent. Your receiver may see retries if the first delivery is not acknowledged.

Return a 2xx response only after your integration has safely accepted the event.