Guides/Webhooks

Webhooks

Rather than polling for changes, register one endpoint from Settings → Webhooks and RideLoop will POST a signed JSON payload there whenever a subscribed event happens — a booking gets cancelled by a guest, a payment fails, a dispute opens.

Event catalog

typemeaning
booking.createdA new booking was created.
booking.confirmedA booking was confirmed.
booking.cancelledA booking was cancelled.
booking.completedA booking finished (return/checkout complete).
booking.rescheduledA guest accepted a Ride Guarantee weather reschedule.
payment.receivedA payment (deposit or full) was received.
payment.failedA payment attempt failed.
payment.refundedA refund was issued.
dispute.createdA card dispute was opened.
dispute.closedA card dispute was resolved.

Booking-related payloads use the exact same shape as GET /api/v1/bookings/{reference} — you see the same booking whether you polled for it or got pushed it.

Verifying the signature

Every delivery carries a RideLoop-Signature header:

RideLoop-Signature: t=1735689600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

t is the Unix timestamp the request was sent, and v1 is an HMAC-SHA256 hex digest of ${t}.${raw request body}, keyed with your endpoint’s signing secret (shown once you add an endpoint, and always re-viewable from Settings → Webhooks). Recompute it yourself and compare — never trust an unsigned payload:

import crypto from 'node:crypto'
function verifyRideLoopSignature(rawBody: string, signatureHeader: string, secret: string): boolean {
const [tPart, v1Part] = signatureHeader.split(',')
const timestamp = tPart.split('=')[1]
const signature = v1Part.split('=')[1]
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}

Use the raw, unparsed request body when recomputing the signature — re-serializing a parsed JSON object can produce different bytes and a signature mismatch.

Retries

A delivery counts as successful on any 2xx response. Anything else — a non-2xx status, a timeout, a connection error — is retried automatically: immediately, then roughly 1 minute, 5 minutes, 30 minutes, and 2 hours later. After 5 total attempts with no success, the event is marked EXHAUSTED and no further attempts are made — check your delivery log in Settings → Webhooks if you see events piling up there.

Testing your endpoint

Use the “Send test event” button in Settings → Webhooks to trigger a synthetic test.ping delivery against your configured endpoint at any time — signed exactly like a real event, so it’s a genuine end-to-end check of your signature verification.