For developers
Webhooks
Signed HTTPS callbacks when reservations, dispatch, drivers, customers and payments change, with retries and a delivery log.
Creating an endpoint
An owner or admin adds endpoints in the dashboard under Settings, Integrations, Webhooks, or you can manage them with the /api/v1/webhooks routes. Choose exact event names; wildcards such as booking.* are rejected. When the endpoint is created the dashboard shows its signing secret once. Store it in your secret manager.
Endpoints must be public https:// URLs. Private, loopback and link-local hosts are refused when the endpoint is saved, and the host is resolved again before each delivery.
Event types
Subscribe to any of these exact names.
- To follow assignment, subscribe to
dispatch.assignment_createdorbooking.status_changed.
| Area | Events |
|---|---|
| Reservations | booking.cancelled, booking.completed, booking.created, booking.status_changed, booking.updated |
| Dispatch | dispatch.assignment_cancelled, dispatch.assignment_created, dispatch.assignment_transitioned, dispatch.manual_assignment, dispatch.offer_accepted, dispatch.offer_cancelled, dispatch.offer_created, dispatch.offer_expired, dispatch.offer_rejected, dispatch.offer_responded |
| Drivers | driver.status_changed, driver.suspended, driver.unsuspended, driver.updated |
| Customers | passenger.archived, passenger.blacklisted, passenger.created, passenger.unblacklisted, passenger.updated |
| Payments | payment.captured, payment.created, payment.failed, payment.refund_requested, payment.refunded |
Payload and headers
Each delivery is a POST with a JSON body in snake_case. event_id is the id of the record the event is about (the booking, driver, customer or payment), not a unique id for the event.
| Header | Meaning |
|---|---|
X-Renaro-Signature | t=<ISO-8601 timestamp>,v1=<hex HMAC-SHA256> |
X-Renaro-Timestamp | The same timestamp as t |
X-Renaro-Event-Type | The event name, e.g. booking.status_changed |
X-Renaro-Event-Id | The id of the record the event is about |
X-Renaro-Delivery-Id | Unique per delivery, unchanged across its retries |
X-Renaro-Attempt | 1 for the first attempt, then 2, 3 and so on |
X-Renaro-Request-Id | The API request that caused the event, when there was one |
User-Agent | Renaro-Outbound-Webhooks/1.0 |
{
"event_type": "booking.status_changed",
"event_id": "b6f1c2d4-3e5a-4c7b-9d1e-2f3a4b5c6d7e",
"occurred_at": "2026-10-02T14:05:12.481Z",
"data": { "...": "..." }
} Verifying the signature
The signature is a hex HMAC-SHA256, keyed with the endpoint's signing secret, over the string t + "." + rawBody. Unlike Stripe's scheme, t is an ISO-8601 timestamp, not Unix seconds. Verify against the raw request bytes before parsing the JSON, compare in constant time, and reject deliveries whose timestamp is more than five minutes from your clock. Checking the signature alone would let a captured request be replayed later; the tolerance window closes that.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyRenaroWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((part) => {
const index = part.indexOf("=");
return [part.slice(0, index).trim(), part.slice(index + 1).trim()];
}),
);
const sentAt = Date.parse(parts.t);
if (!Number.isFinite(sentAt) || Math.abs(Date.now() - sentAt) > toleranceSeconds * 1000) return false;
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const received = Buffer.from(parts.v1 ?? "", "utf8");
const wanted = Buffer.from(expected, "utf8");
return received.length === wanted.length && timingSafeEqual(received, wanted);
} Retries and failures
A delivery succeeds on any 2xx response within 30 seconds. Network errors, timeouts and 408, 409, 425, 429 or 5xx responses are retried with exponential backoff: one second, doubling each time, capped at 15 minutes. By default an endpoint gets five attempts in total (configurable from 0 to 20). Other 4xx responses are not retried. After 100 consecutive failures the endpoint is disabled.
The dashboard's delivery log and GET /api/v1/webhooks/:id/deliveries show each attempt with its status code, and you can send a test event with POST /api/v1/webhooks/:id/test.
Handling deliveries safely
- Respond
2xxquickly and do the work afterwards; slow handlers hit the 30-second timeout and are retried. - Use
X-Renaro-Delivery-Idto ignore a retry you have already processed. - Deliveries are keyed by endpoint, event type and record, so a later event of the same type for the same record may not be delivered to that endpoint again. Treat a webhook as a prompt to fetch the record's current state from the API, not as a complete change log.
- Deliveries can arrive out of order. Compare
occurred_at, or re-read the record, before overwriting newer data.