// Developers
Drop Range Waiver
into your platform.
Build it in an afternoon. POS, eCommerce, range-management — one bearer key, one POST to mint a signing link, one webhook when the signer is done. The audit-packet PDF is signed and ready for your customer file.
// v1 · base url: app.rangewaiver.com · openapi.json
// Mental model
Two steps, one webhook.
Range Waiver gives you a signed bearer key per Range Waiver business. You mint a signing link scoped to that business, pass your customer to it, and receive a webhook when they finish. The signed audit-packet PDF arrives in the same payload.
01
Mint a signing link
POST /api/v1/signing-links with the template + optional prefill + your external_customer_id. Returns a short-lived signing URL.
02
Send the customer
Open the URL on a kiosk, a phone, or inside an iframe via the embed widget. We handle signature canvas, ID scan, photo, attestations.
03
Handle the webhook
waiver.signed arrives with the full record + a 24-hour signed PDF URL. Verify the HMAC signature, attach the PDF to your customer file, done.
// Authentication
Bearer keys. One per business.
Your Range Waiver customer generates an API key in their admin under API keys. The plaintext secret is shown exactly once — they paste it into your installation form. We store only the SHA-256 hash.
GET /api/v1/waivers Host: app.rangewaiver.com Authorization: Bearer rw_abcd1234_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- ▸ Format:
rw_<prefix>_<secret>— prefix shown in admin for identification. - ▸ Revocation is immediate. Lost a secret? Revoke and reissue.
- ▸ Rate limits: 10 req/s, 300 req/min.
429responses carry standardX-RateLimit-*headers.
// REST · v1
The endpoints you'll actually use.
/api/v1/signing-linksMint a signing URL
The workhorse. Returns a signed URL the signer opens. Pass prefill data + your external id; we echo it back on the webhook so the integration stays stateless on your side.
curl -X POST https://app.rangewaiver.com/api/v1/signing-links \
-H "Authorization: Bearer $RW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_public_id": "8c3d-...-uuid",
"location_id": 42,
"expires_in": "1h",
"external_customer_id": "pos-customer-12345",
"return_url": "https://your-pos.example.com/lanes/3?signed=1",
"prefill": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone": "+12055550100",
"dob": "1990-04-12"
},
"metadata": { "lane": "3", "ticket_id": "abc" }
}'
# response
{
"url": "https://app.rangewaiver.com/sign/eyJhbGciOi...",
"token": "eyJhbGciOi...",
"expires_in": "1h"
}/api/v1/templatesList templates
The template_public_id you pass to /signing-links comes from here. Stable across renames + version bumps.
{
"data": [
{
"id": 123,
"public_id": "8c3d-...-uuid",
"title": "Range Liability Waiver",
"version": 4,
"active": true,
"updated_at": "2026-05-26T13:32:00Z"
}
]
}/api/v1/waiversList signed waivers
Paginated. Use this to reconcile if you missed a webhook.
GET /api/v1/waivers?limit=50&offset=0
{
"data": [
{
"id": 9001,
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone": "+12055550100",
"signed_at": "2026-05-26T13:32:00Z",
"packet_hash": "sha256:b3f0...",
"template_id": 123,
"location_id": 42
}
],
"limit": 50,
"offset": 0
}/api/v1/waivers/[id]/pdfAudit-packet PDF
302 to a 15-minute signed CloudFront URL. Follow the redirect or hand the URL straight to the user. The same URL arrives pre-signed (24h) inside waiver.signed webhooks.
HTTP/1.1 302 Found Location: https://cdn.rangewaiver.com/packets/...?signature=...&expires=...
/api/v1/customersCustomer search (collapsed by identity)
Returns one row per (first_name, last_name, email) tuple. Use this before minting a new signing link to surface "Welcome back" UX.
GET /api/v1/customers?q=jane@example.com&limit=25
{
"data": [
{
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone": "+12055550100",
"last_signed_at": "2026-05-26T13:32:00Z",
"signed_count": 4,
"latest_waiver_id": 9001
}
]
}/api/v1/webhook-endpointsWebhook endpoint CRUD
GET / POST / PATCH / DELETE. The HMAC secret is returned ONCE on POST — capture it. Generally easier to manage from the admin UI.
POST /api/v1/webhook-endpoints
{
"url": "https://your-platform.com/integrations/rangewaiver/wh",
"events": ["waiver.signed", "incident.created"],
"active": true
}
# response
{
"id": 17,
"url": "https://your-platform.com/integrations/rangewaiver/wh",
"events": ["waiver.signed", "incident.created"],
"active": true,
"secret": "whsec_3f0a..." // shown once — store securely
}// Webhooks
At-least-once. HMAC-signed. Retried 5×.
Three events. JSON. HMAC-SHA256 in X-RangeWaiver-Signature. Idempotent on delivery_id. Retries on non-2xx or timeout with exponential backoff (1s → 10s → 1m → 10m → 1h).
waiver.signedNew signing completed via web link, kiosk, or embed widget.
waiver.reattestedReturning customer re-affirmed the safety blocks per template cadence.
incident.createdRSO or admin filed an incident report from the kiosk or admin.
POST https://your-platform.com/integrations/rangewaiver/wh
X-RangeWaiver-Event: waiver.signed
X-RangeWaiver-Delivery-Id: 8821
X-RangeWaiver-Signature: 3a9e... // hmac-sha256(body, whsec_...)
Content-Type: application/json
{
"event": "waiver.signed",
"event_version": 1,
"delivery_id": 8821,
"timestamp": "2026-05-26T13:32:00.123Z",
"data": {
"waiver_id": 9001,
"template_id": 123,
"template_version": 4,
"location_id": 42,
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone": "+12055550100",
"dob": "1990-04-12",
"signed_at": "2026-05-26T13:32:00Z",
"packet_hash": "sha256:b3f0...",
"audit_packet_url": "https://cdn.rangewaiver.com/packets/...",
"external_customer_id": "pos-customer-12345",
"metadata": { "lane": "3", "ticket_id": "abc" }
}
}import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyRangeWaiverSignature(
rawBody: string,
header: string,
secret: string,
): boolean {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(header, "utf8");
const b = Buffer.from(expected, "utf8");
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}// Embed widget
One script tag. Drop the signing flow anywhere.
For eCommerce checkout and customer-facing portals. The script injects an iframe + handles height resizing via postMessage. Templates must be active.
<script src="https://app.rangewaiver.com/embed.js" data-template="8c3d-the-template-public-id"></script>
window.addEventListener("message", (e) => {
if (e.origin !== "https://app.rangewaiver.com") return;
if (e.data?.kind === "rw:signed") {
// e.data.waiver_id, e.data.signed_at, e.data.external_customer_id
unlockCheckout();
}
});// What's next
Roadmap.
// ready to build?
Ship it this afternoon.
Sign up free, generate an API key from your admin, point your sandbox at our endpoints. Need help? Email integrations@rangewaiver.com.