Skip to contentSkip to Content

Webhooks API

Subscribe to real-time events from your Rentalot account. When events occur (new inquiry, message sent, showing booked, etc.), Rentalot sends a POST request to your URL with an HMAC-signed JSON payload.

Event Types

EventDescription
inquiry.createdA new contact reached out for the first time
message.receivedAn inbound message was received from a contact
message.sentAn outbound message was sent to a contact
showing.bookedA showing was scheduled
showing.cancelledA showing was cancelled
showing.deletedA showing was hard-deleted
event.createdA calendar event was created
event.cancelledA calendar event was cancelled
event.deletedA calendar event was deleted
contact.updatedA contact’s details were updated
property.updatedA property listing was updated (incl. images)
workflow.createdA new workflow template was created
workflow.updatedA workflow template was updated
workflow.deletedA workflow template was deleted
workflow.completedA workflow run finished
bulk_import.completedA bulk-property-import job finished

List Webhooks

GET /api/v1/webhooks

Response (200 OK):

{ "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://example.com/webhooks/rentalot", "events": ["inquiry.created", "showing.booked"], "active": true, "description": "CRM sync", "lastDeliveredAt": "2026-02-11T14:30:00.000Z", "createdAt": "2026-02-01T10:00:00.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 2, "totalPages": 1 } }

Create Webhook

POST /api/v1/webhooks

Request body:

{ "url": "https://example.com/webhooks/rentalot", "events": ["inquiry.created", "message.received", "showing.booked"], "description": "CRM sync webhook" }
FieldTypeRequiredDescription
urlstringYesHTTPS URL to receive events. Private/loopback IPs are rejected (SSRF protection).
eventsstring[]YesAt least one event type from the list above.
descriptionstringNoHuman-readable label (max 500 chars).

Supports Idempotency-Key.

Response (201 Created):

{ "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://example.com/webhooks/rentalot", "events": ["inquiry.created", "message.received", "showing.booked"], "active": true, "description": "CRM sync webhook", "secret": "a1b2c3d4e5f6...", "createdAt": "2026-02-01T10:00:00.000Z" } }

The secret is only returned on create and rotate. Save it — you’ll need it to verify webhook signatures.

Get Webhook

GET /api/v1/webhooks/:id

Response (200 OK): { "data": <webhook> } — no secret field.

Update Webhook

PATCH /api/v1/webhooks/:id
{ "events": ["inquiry.created", "showing.booked"], "active": false }
FieldTypeDescription
urlstringNew HTTPS endpoint
eventsstring[]Replace subscribed events
activebooleanEnable or disable delivery
descriptionstringUpdate label

Delete Webhook

DELETE /api/v1/webhooks/:id

Returns 204 No Content.

Test Webhook

POST /api/v1/webhooks/:id/test

Sends a synthetic test.ping event to the webhook URL with a payload of {"message":"This is a test webhook delivery from Rentalot"}. Use this to verify your endpoint is reachable and correctly handling payloads.

Response (200 OK):

{ "data": { "delivered": true, "event": "test.ping" } }

delivered: false means the URL responded with a non-2xx status, timed out, or failed the SSRF check.

Rotate Webhook Secret

POST /api/v1/webhooks/:id/rotate-secret

Generates a new HMAC signing secret. Resets consecutiveFailures to 0. The new secret is returned once in the response — save it immediately. The old secret stops working as soon as the rotation completes.

Response (200 OK):

{ "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://example.com/webhooks/rentalot", "events": ["inquiry.created"], "active": true, "description": "CRM sync", "secret": "new-secret-here..." } }

Payload Format

Every webhook delivery is a POST request with a JSON body:

{ "event": "showing.booked", "data": { "id": "660e8400-e29b-41d4-a716-446655440000" }, "timestamp": "2026-02-11T14:30:00.000Z", "webhookId": "9f1e1f2a-...", "version": "2026-02-27" }
FieldDescription
eventEvent type — one of the values in the table above.
dataEvent-specific payload. For most events this is { id } referencing the affected resource.
timestampISO 8601 server timestamp.
webhookIdStable per-delivery UUID. Identical across retries — use it to dedupe on your side.
versionWebhook API version (date-based; bumped on breaking payload changes).

Verifying Signatures

Every delivery includes these headers:

HeaderDescription
X-Webhook-Signaturesha256={HMAC-SHA256 hex digest of the raw request body}
X-Webhook-IdSame as webhookId in the body — stable across retries.
X-Webhook-TimestampISO 8601 delivery timestamp (matches timestamp in the body).

Compute the HMAC-SHA256 of the raw request body using your webhook secret and compare with the signature header:

import crypto from "node:crypto"; function verifyWebhook(body, signature, secret) { const expected = crypto .createHmac("sha256", secret) .update(body) .digest("hex"); return signature === `sha256=${expected}`; }

Always use a constant-time comparison (e.g. crypto.timingSafeEqual) in production.

Retry Policy

Failed deliveries (non-2xx response, timeout > 10 s, or SSRF rejection) are retried with exponential backoff. After 10 consecutive failures the subscription is auto-deactivated — re-enable it via PATCH with active: true after fixing your endpoint, or call rotate-secret (which also resets the failure counter).

Idempotency

POST /api/v1/webhooks and POST /api/v1/webhooks/:id/rotate-secret accept the Idempotency-Key header.