Guides/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.
| type | meaning |
|---|---|
| booking.created | A new booking was created. |
| booking.confirmed | A booking was confirmed. |
| booking.cancelled | A booking was cancelled. |
| booking.completed | A booking finished (return/checkout complete). |
| booking.rescheduled | A guest accepted a Ride Guarantee weather reschedule. |
| payment.received | A payment (deposit or full) was received. |
| payment.failed | A payment attempt failed. |
| payment.refunded | A refund was issued. |
| dispute.created | A card dispute was opened. |
| dispute.closed | A 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.
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.
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.
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.