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.
| Operation | Request | Permission |
|---|---|---|
listWebhookSubscriptions | GET /webhook-subscriptions | none beyond a valid credential |
createWebhookSubscription | POST /webhook-subscriptions | depends on the event |
deleteWebhookSubscription | DELETE /webhook-subscriptions | none beyond a valid credential |
listWorkflowZapierSteps | GET /workflow-zapier-steps | view_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.
Subscriptions belong to your credential
Section titled “Subscriptions belong to your credential”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).
Events
Section titled “Events”| Event | Fires when | Needs | data is shaped like |
|---|---|---|---|
reservation.created | A new confirmed reservation with a current or upcoming stay arrives in SuiteOp, or is linked to a property | view_reservations | getReservation |
reservation.cancelled | A reservation is cancelled | view_reservations | getReservation |
reservation.dates_changed | A reservation’s check-in or check-out changes | view_reservations | getReservation |
reservation.precheckin_submitted | The guest submits pre-check-in | view_reservations | getReservation |
task.created | A task is created | view_tasks | getTask |
task.assigned | A task gets a new assignee — at creation, on reassignment or by auto-assignment; not on unassignment | view_tasks | getTask |
task.started | A task is started | view_tasks | getTask |
task.completed | A task is completed | view_tasks | getTask |
task.cancelled | A task is cancelled | view_tasks | getTask |
device.offline | A device goes offline | view_devices | getDevice |
device.reconnected | A device that went offline has been back online for about 10 minutes (Netatmo: as soon as it reconnects) | view_devices | getDevice |
review.submitted | A guest review arrives — a guest-portal survey, or a new review synced from your PMS | view_reservations | getReview |
workflow.step | A chosen Send to Zapier step in a workflow runs (OAuth only) | view_workflows | see 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 steps
Section titled “Workflow steps”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 with403. filters.workflowStepis required, and names the step as<workflow id>:<node id>. Get the ids fromlistWorkflowZapierSteps: it lists the Send to Zapier steps you can see, from each workflow’s draft and live versions, with anamethat reads<workflow name> → <step label>. It pages withlimit(1–100, default 50) andoffset. An id that matches no step you can see answers404.- A non-empty
filters.propertyIdsis refused; the workflow decides which runs reach the step.filters.workflowStepis refused with any other event. datais{ "workflow": { "id", "name" }, "step": { "id", "label" }, "record_type", "data" }.record_typeis the record the run was about —reservation,task,review,deviceorupsell— anddatais that record as its get operation returns it. Both arenullwhen 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.
Creating a subscription
Section titled “Creating a subscription”Each subscription is one event and one URL. To receive several events, create one subscription per event.
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 answers403.targetUrl— must behttps, 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 fromlistProperties. 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.
What SuiteOp sends
Section titled “What SuiteOp sends”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.
Responding and retries
Section titled “Responding and retries”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.
When a subscription stops
Section titled “When a subscription stops”SuiteOp disables a subscription, and stops delivering to it, when:
- 50 deliveries in a row have failed. A successful delivery resets the count;
consecutiveFailureson 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.
Removing subscriptions
Section titled “Removing subscriptions”Pass exactly one of id or target_url as a query parameter:
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.