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
| Event | Description |
|---|---|
inquiry.created | A new contact reached out for the first time |
message.received | An inbound message was received from a contact |
message.sent | An outbound message was sent to a contact |
showing.booked | A showing was scheduled |
showing.cancelled | A showing was cancelled |
showing.deleted | A showing was hard-deleted |
event.created | A calendar event was created |
event.cancelled | A calendar event was cancelled |
event.deleted | A calendar event was deleted |
contact.updated | A contact’s details were updated |
property.updated | A property listing was updated (incl. images) |
workflow.created | A new workflow template was created |
workflow.updated | A workflow template was updated |
workflow.deleted | A workflow template was deleted |
workflow.completed | A workflow run finished |
bulk_import.completed | A bulk-property-import job finished |
List Webhooks
GET /api/v1/webhooksResponse (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/webhooksRequest body:
{
"url": "https://example.com/webhooks/rentalot",
"events": ["inquiry.created", "message.received", "showing.booked"],
"description": "CRM sync webhook"
}| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS URL to receive events. Private/loopback IPs are rejected (SSRF protection). |
events | string[] | Yes | At least one event type from the list above. |
description | string | No | Human-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/:idResponse (200 OK): { "data": <webhook> } — no secret field.
Update Webhook
PATCH /api/v1/webhooks/:id{
"events": ["inquiry.created", "showing.booked"],
"active": false
}| Field | Type | Description |
|---|---|---|
url | string | New HTTPS endpoint |
events | string[] | Replace subscribed events |
active | boolean | Enable or disable delivery |
description | string | Update label |
Delete Webhook
DELETE /api/v1/webhooks/:idReturns 204 No Content.
Test Webhook
POST /api/v1/webhooks/:id/testSends 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-secretGenerates 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"
}| Field | Description |
|---|---|
event | Event type — one of the values in the table above. |
data | Event-specific payload. For most events this is { id } referencing the affected resource. |
timestamp | ISO 8601 server timestamp. |
webhookId | Stable per-delivery UUID. Identical across retries — use it to dedupe on your side. |
version | Webhook API version (date-based; bumped on breaking payload changes). |
Verifying Signatures
Every delivery includes these headers:
| Header | Description |
|---|---|
X-Webhook-Signature | sha256={HMAC-SHA256 hex digest of the raw request body} |
X-Webhook-Id | Same as webhookId in the body — stable across retries. |
X-Webhook-Timestamp | ISO 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.