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.
Available Events
Subscribe to the events your integration needs.
waiver.submittedFired when a waiver is signed and submitted
waiver.verifiedFired when a submitted waiver is successfully email verified
waiver.expiring_soonFired when an unverified waiver is approaching its verification expiry window
waiver.expiredFired when a waiver is marked expired by the expiration cron
waiver.updatedFired when an existing waiver is modified (status, notes, compliance)
waiver.deletedFired when a waiver is deleted (GDPR compliance)
template.createdFired when a waiver template is created
template.updatedFired when a waiver template is modified
template.archivedFired when a waiver template is archived instead of hard deleted, usually because signed waivers reference it
template.deletedFired when an unused waiver template is permanently deleted
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 timestampX-Webhook-DeliveryUnique delivery identifier. Use this for idempotent processing when present.Content-Typeapplication/jsonSignature 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.
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.
