Skip to content
Dashboard

Webhooks

A webhook subscription tells SuiteOp to POST to your https endpoint whenever an event happens — a reservation is created, a task is completed, a lock goes offline. Use it instead of polling the list endpoints. Every field is listed on the operation’s page in the API Reference.

OperationRequestPermission
listWebhookSubscriptionsGET /webhook-subscriptionsnone beyond a valid credential
createWebhookSubscriptionPOST /webhook-subscriptionsdepends on the event
deleteWebhookSubscriptionDELETE /webhook-subscriptionsnone beyond a valid credential
listWorkflowZapierStepsGET /workflow-zapier-stepsview_workflows

Paths are relative to your region’s base URL, for example https://api-us.suiteop.com/api/v1. Webhook subscriptions are managed over REST only; they aren’t available as MCP tools.

A subscription is owned by the credential that created it, not by the organization: an API key, or for OAuth the app together with the user who authorized it. listWebhookSubscriptions and deleteWebhookSubscription only ever see your own credential’s subscriptions; another key, another app, or another user who authorized the same app in your organization has its own separate set.

Deliveries run as that credential too. SuiteOp reads the entity with your credential’s current permissions and property scope, exactly as a REST call would, so revoking the key or removing a permission it relied on stops the deliveries (see When a subscription stops).

EventFires whenNeedsdata is shaped like
reservation.createdA new confirmed reservation with a current or upcoming stay arrives in SuiteOp, or is linked to a propertyview_reservationsgetReservation
reservation.cancelledA reservation is cancelledview_reservationsgetReservation
reservation.dates_changedA reservation’s check-in or check-out changesview_reservationsgetReservation
reservation.precheckin_submittedThe guest submits pre-check-inview_reservationsgetReservation
task.createdA task is createdview_tasksgetTask
task.assignedA task gets a new assignee — at creation, on reassignment or by auto-assignment; not on unassignmentview_tasksgetTask
task.startedA task is startedview_tasksgetTask
task.completedA task is completedview_tasksgetTask
task.cancelledA task is cancelledview_tasksgetTask
device.offlineA device goes offlineview_devicesgetDevice
device.reconnectedA device that went offline has been back online for about 10 minutes (Netatmo: as soon as it reconnects)view_devicesgetDevice
review.submittedA guest review arrives — a guest-portal survey, or a new review synced from your PMSview_reservationsgetReview
workflow.stepA chosen Send to Zapier step in a workflow runs (OAuth only)view_workflowssee Workflow steps

reservation.created also fires when an existing reservation becomes confirmed later — an inquiry turning into a booking, for example. It doesn’t fire for the existing reservations SuiteOp imports in the initial sync after you connect a PMS or calendar, or when a reservation moves to a different property. It can fire a second time for the same reservation if its property is removed and later set again.

review.submitted fires once per review. It doesn’t fire for the review history SuiteOp imports the first time it syncs reviews from a PMS account, or when a guest later edits a review that was already synced. Its data carries the overall and category ratings (1–5, null when the guest gave none), the public text, and short-lived signed photoUrls; the private feedback (privateReview) is included only when your credential also holds view_analytics. To re-read a review later, call getReview (GET /reviews/{id}) with the id from the delivery — see Reviews.

workflow.step fires when a specific Send to Zapier step in one of your workflows runs. It works differently from the events above:

  • It needs an OAuth connection and view_workflows. A request from an API key is refused with 403.
  • filters.workflowStep is required, and names the step as <workflow id>:<node id>. Get the ids from listWorkflowZapierSteps: it lists the Send to Zapier steps you can see, from each workflow’s draft and live versions, with a name that reads <workflow name> → <step label>. It pages with limit (1–100, default 50) and offset. An id that matches no step you can see answers 404.
  • A non-empty filters.propertyIds is refused; the workflow decides which runs reach the step. filters.workflowStep is refused with any other event.
  • data is { "workflow": { "id", "name" }, "step": { "id", "label" }, "record_type", "data" }. record_type is the record the run was about — reservation, task, review, device or upsell — and data is that record as its get operation returns it. Both are null when the run had no record.

Only view_workflows is needed to subscribe, so a run whose record your credential can’t read is skipped rather than failing the subscription. A run of a workflow you can no longer see, or one that was deleted, is skipped too. Losing view_workflows disables the subscription.

Each subscription is one event and one URL. To receive several events, create one subscription per event.

Terminal window
curl -X POST "https://api-us.suiteop.com/api/v1/webhook-subscriptions" \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"event": "task.completed",
"targetUrl": "https://example.com/hooks/suiteop/REPLACE-WITH-A-LONG-RANDOM-SECRET",
"filters": { "propertyIds": ["<property-id>"] }
}'
  • event — one of the events above. Your credential must hold the permission the table lists for it, or the create answers 403.
  • targetUrl — must be https, must not embed a username or password, and must not point at a private or loopback host.
  • filters.propertyIds — optional, up to 100 property ids from listProperties. Events for other properties aren’t delivered. Omit it, or leave it empty, to receive the event for every property your credential can see.

A credential can have at most 100 active subscriptions; the next create answers 409. Disabled subscriptions don’t count toward that limit.

Each delivery is a POST with Content-Type: application/json and User-Agent: SuiteOp-Webhooks/1:

{
"id": "0c9a6f1e-5d3b-4b8e-9a51-2f0b7c1d4e6a",
"event": "task.completed",
"occurredAt": "2026-09-27T14:02:11.000Z",
"data": { "id": "…", "…": "the task, as getTask returns it" }
}

data is read when the delivery is sent, not when the event happened, so it reflects the entity’s current state. If the entity has since been deleted, or is outside your credential’s property scope, nothing is sent, and that doesn’t count as a failure.

Use id to de-duplicate: a retried delivery keeps the same id, and your endpoint may occasionally receive the same event more than once.

Answer with any 2xx status within 10 seconds. SuiteOp doesn’t read your response body and doesn’t follow redirects — a 3xx counts as a failure.

A failed attempt (a non-2xx status, a timeout or a connection error) is retried with exponential backoff starting at up to 30 seconds, up to 5 attempts in total. A delivery whose last attempt fails counts as one failed delivery.

Answer 410 Gone to unsubscribe: SuiteOp deletes the subscription. A delivery already in flight may still arrive once.

SuiteOp disables a subscription, and stops delivering to it, when:

  • 50 deliveries in a row have failed. A successful delivery resets the count; consecutiveFailures on the subscription shows where it stands.
  • The credential that owns it is no longer valid — the API key was revoked or expired, the member it acts as was deactivated, or the OAuth app was disconnected, re-authorized for another organization, or acts as a member who no longer meets your organization’s two-factor rule. An API key isn’t disabled by the two-factor rule.
  • The credential lost the permission the event needs.
  • The target host resolves to a private or loopback address at delivery time.

A disabled subscription stays in listWebhookSubscriptions with disabledAt set. It isn’t re-enabled: once the cause is fixed, create a new subscription, with a replacement credential if the old one was revoked or expired. Delete the disabled one while its own credential still works; a different credential can’t see or delete it. lastDeliveredAt shows the last successful delivery.

Pass exactly one of id or target_url as a query parameter:

Terminal window
curl -G -X DELETE "https://api-us.suiteop.com/api/v1/webhook-subscriptions" \
--data-urlencode "target_url=https://example.com/hooks/suiteop/REPLACE-WITH-A-LONG-RANDOM-SECRET" \
-H "Authorization: Bearer sk_live_your_key_here"

By target_url, every subscription of your credential that posts to that exact URL is removed, whatever its event. The response’s data is { "deleted": <count> }; you get 404 when nothing matched. Deliveries already queued may still arrive once after you delete.