Webhooks push what happens in an account — bookings, payments, waivers, carts, POS sales — to a URL you control, the moment it happens, with no polling. RentalTide signs every delivery and retries failures with exponential backoff.
There are two ways to receive them:
- Your own endpoint (Settings → Webhooks). Any merchant can register a URL, tick the events they want, and point it at Zapier, Make, n8n, a CRM or their own server. No app, no scopes, no code on our side.
- A marketplace app. An installed app with the
webhooks:receivescope gets the same deliveries at the app's registered webhook URL.
Both use the same payload, the same signature scheme and the same retry ladder.
Setup — your own endpoint
- Go to Settings → Webhooks and click Add endpoint.
- Paste the URL. It must be reachable from the public internet — localhost and private/internal addresses are rejected when you save, not silently later.
- Tick the events: individual names, a whole domain (All bookings), or Everything — which also covers events added in future releases.
- Copy the signing secret. It is shown in full when the endpoint is created and whenever you rotate it, and can be re-shown from the endpoint's panel.
- Hit Send test to fire a signed sample payload and see exactly what your server answered.
Each endpoint gets its own secret; rotating one never affects another. Rotating takes effect immediately — update your consumer first, or the next delivery fails signature verification.
Setup — marketplace app
- Register a Webhook URL and a Webhook secret on your app in the partner portal.
- Request the
webhooks:receivescope. - Verify the signature on every request before trusting it.
Use the Test webhook button in the developer portal to send a signed sample delivery to your endpoint at any time.
Event catalog
Subscribe to an exact name (booking.created), a whole domain (booking.*), or everything (*).
| Event | When |
|---|---|
booking.created | A booking was created — online, walk-up, internal or agent. |
booking.updated | A booking changed with no more specific event. |
booking.status_changed | A booking moved to a new status. |
booking.checked_in | A booking was checked in. |
booking.completed | The rental finished / the asset came back. |
booking.cancelled | A booking was cancelled. |
booking.rescheduled | A booking was rescheduled. |
booking.note_added | Staff added a note to a booking. |
payment.received | A payment was recorded against a booking. |
payment.refunded | A refund was issued. |
payment.charge_added | An extra charge (fuel, damage, add-on) was posted to a booking. |
cart.abandoned | A customer started a booking online and did not finish. |
cart.recovered | An abandoned cart converted into a booking. |
customer.created | A new customer was created. |
customer.updated | A customer record was updated. |
waiver.completed | A participant signed a waiver — one per signer, not one per booking. |
membership.created | A membership was sold or started. |
membership.status_changed | A membership changed status. |
membership.cancelled | A membership was cancelled or expired. |
giftcard.issued | A gift card was purchased or issued. |
giftcard.redeemed | Gift card balance was spent. |
pos.sale_completed | A POS sale was completed. |
pos.refund_issued | A POS sale was refunded. |
asset.launch_detected | A tracked asset crossed its launch zone (direction: out / in). |
tour.status_changed | A tour departure changed status. |
tour.cancelled | A tour departure was cancelled. |
staff.clocked_in | A staff member clocked in. |
staff.clocked_out | A staff member clocked out. |
app.installed | Your app was installed by a merchant (provision here). |
app.uninstalled | Your app was uninstalled (deprovision / clean up here). |
app.installed / app.uninstalled go to marketplace apps only.
The live list is always available at GET /webhooks/events, and the same catalog ships as WEBHOOK_EVENT_CATALOG in @rentaltide/app-sdk.
Payload
Every delivery is a JSON POST:
Verify the signature
The signature is v1= + HMAC_SHA256(secret, "{X-RentalTide-Timestamp}.{rawBody}") in hex. Compute it over the raw request body and compare in constant time. Reject deliveries whose timestamp is more than 5 minutes old (replay protection).
A complete runnable receiver is in the with-backend example.
Delivery & retries
- Respond
2xxquickly to acknowledge. Do slow work asynchronously. - Non-2xx (or a timeout) is retried with exponential backoff — 1m → 5m → 15m → 1h → 6h — up to 6 attempts, then the delivery is marked dead.
- Deliveries can arrive more than once; make your handler idempotent (e.g. dedupe on the
dataids). We collapse repeat emits of a single change, but network-level retries can still duplicate a delivery your server already processed but failed to acknowledge in time. - Every request carries
X-RentalTide-Delivery, a stable id for that delivery — the simplest key to dedupe on.
When your endpoint is down
Each failed delivery is retried through the full ladder before it dies. Ten consecutive dead deliveries auto-disables the endpoint — we stop sending, the Webhooks page shows it as Auto-disabled with the last error, and you turn it back on once the endpoint is fixed. A single success resets the counter.
Delivery log
Every endpoint keeps a log of what we sent, what your server answered, how many attempts it took, and the exact payload. Any delivery can be replayed from there — useful after fixing a handler bug, without waiting for the event to happen again.

