Getting Started with Sisu Webhooks

This guide shows how to set up your team to receive Sisu events, such as transaction, user and task changes. Sisu publishes these events to an Amazon SNS Topic. You subscribe to that Topic with one or more URL endpoints or email addresses, and each one receives the messages.

Configuration

  1. In Sisu, go to Admin > Team Settings.
  2. In the Team Settings menu, click API Credentials/Webhooks.
  1. Click Generate API Credentials to generate your credentials.
  2. Click Generate Webhook Topic.

Once your Webhook Topic exists, you can subscribe endpoints to it.

To add a subscription, enter a display name, choose a protocol and enter the endpoint:

ProtocolEndpoint exampleWhat happens next
email, email-json[email protected]AWS emails a confirmation link. Click it to confirm the subscription.
http, httpshttps://example.com/webhooks/sisuAWS sends a SubscriptionConfirmation message to the URL, followed by Notification messages once you confirm. Your endpoint must be live before you subscribe it. See Confirming a webhook.

The subscriptions table lists each subscription and lets you remove it. AWS only delivers messages to confirmed subscriptions.

📘

Use https

Event payloads contain client and agent contact details. Use an https endpoint for anything other than local testing.

Confirming a webhook

Before AWS delivers events to a new URL, the URL's owner has to confirm the subscription. This keeps anyone from pointing your Topic at an endpoint that doesn't expect it.

When you add a URL subscription, AWS immediately sends it a SubscriptionConfirmation message. To confirm, send an HTTP GET request to the SubscribeURL in that message. Then refresh the Sisu webhook screen to confirm the subscription shows as confirmed.

AWS deletes subscriptions that aren't confirmed within 48 hours; after that you'll need to add the subscription again. Confirmed subscriptions don't expire.

Every request AWS sends carries an x-amz-sns-message-type header (also available as the Type field in the body) with one of these values:

ValueMeaningWhat to do
SubscriptionConfirmationA new subscription needs confirming.GET the SubscribeURL.
NotificationA Sisu event or test message.Process it. See Reading the payload.
UnsubscribeConfirmationThe subscription was removed.Nothing is required.

Sample SubscriptionConfirmation request:

POST /webhooks/sisu HTTP/1.1
x-amz-sns-message-type: SubscriptionConfirmation
x-amz-sns-message-id: cd5f3f8c-1a04-42f9-8877-e69f1ca6f17a
x-amz-sns-topic-arn: arn:aws:sns:us-west-2:033563632468:sisu-webhook-886
Content-Type: text/plain; charset=UTF-8
User-Agent: Amazon Simple Notification Service Agent

{
  "Type": "SubscriptionConfirmation",
  "MessageId": "cd5f3f8c-1a04-42f9-8877-e69f1ca6f17a",
  "Token": "2336412f37fb...",
  "TopicArn": "arn:aws:sns:us-west-2:033563632468:sisu-webhook-886",
  "Message": "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:033563632468:sisu-webhook-886.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
  "SubscribeURL": "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=...&Token=...",
  "Timestamp": "2021-10-18T16:46:13.999Z",
  "SignatureVersion": "1",
  "Signature": "EXAMPLE...",
  "SigningCertURL": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-....pem"
}

Test Message

Once you have at least one confirmed subscription, you can send a test message from the webhook screen.

The test message is published as this JSON object:

{
  "test_message": "Test Message"
}

Like any other notification, it may arrive wrapped in an SNS envelope. See Reading the payload.

Setting Up Event Triggers

Once your subscriptions are confirmed, add triggers to choose which events Sisu publishes to your Topic. Every confirmed subscription on the Topic receives every triggered event.

Event types

Triggermodel in payloadPrimary keyCreateUpdateDeleteArchive
Client/TransactionsClientclient_idYesYesYesYes
Agent/UserAgentagent_idYesYesYesYes
TaskClientTasktask_idYesYesYesYes
Check InAiCheckinLoglog_idYesYesYesNo
Custom FieldTeamCustomClientFieldteam_custom_client_field_idYesYesYesNo

The Custom Field trigger fires when a custom field definition is created, edited or deleted, including when its options are added, renamed or removed. A change to a custom field's value on a transaction is a Client update. See Custom field values.

Trigger options

OptionDescription
Trigger on Create / Update / Delete / ArchiveWhich actions publish an event.
Include Full ObjectAdds the record's current state as object_data.full_object.
Use ISO date/time formatSends dates and times in ISO 8601 format instead of the default format. Recommended for new integrations. See Dates and times.

Filtering events

A trigger selects events by type and action only. It can't filter on field values, so filter events in your own code or integration platform. Enable Include Full Object if you need fields that didn't change: updated_values only contains the fields that changed.

Reading the payload

A notification's request body takes one of two shapes, depending on the subscription:

  • The Sisu event payload itself.
  • An SNS Notification envelope. The Sisu event payload is a JSON-encoded string in the envelope's Message field, so you need to parse it a second time.

Bodies are sent as Content-Type: text/plain, so parse the request body as JSON yourself, rather than relying on a framework's JSON body parser. Handle both shapes, as in these examples:

import json

import requests


def handle_sisu_webhook(headers, raw_body: bytes):
    # Header lookups must be case-insensitive (Flask and Django headers already are).
    message_type = headers.get("x-amz-sns-message-type")
    body = json.loads(raw_body)

    if message_type == "SubscriptionConfirmation":
        requests.get(body["SubscribeURL"], timeout=10)
        return
    if message_type != "Notification":
        return

    # SNS envelope: the Sisu payload is a JSON string in "Message".
    if body.get("Type") == "Notification" and "Message" in body:
        body = json.loads(body["Message"])

    if "test_message" in body:
        return

    for change in body["data_objects"]:
        process_change(body["model"], body["action"], change)
// Use a raw/text body parser for this route, e.g. express.text({ type: "*/*" }).
async function handleSisuWebhook(req, res) {
  const messageType = req.get("x-amz-sns-message-type");
  let body = JSON.parse(req.body);

  if (messageType === "SubscriptionConfirmation") {
    await fetch(body.SubscribeURL);
    return res.sendStatus(200);
  }
  if (messageType !== "Notification") return res.sendStatus(200);

  // SNS envelope: the Sisu payload is a JSON string in "Message".
  if (body.Type === "Notification" && "Message" in body) {
    body = JSON.parse(body.Message);
  }

  if (!("test_message" in body)) {
    for (const change of body.data_objects) {
      processChange(body.model, body.action, change);
    }
  }
  res.sendStatus(200);
}

Delivery behavior

  • Respond with a 2xx status quickly, and do slow work after responding. AWS retries deliveries that time out or receive a 5xx response.
  • AWS can deliver the same message more than once, and doesn't guarantee order. Use the envelope's MessageId (or the x-amz-sns-message-id header) to drop duplicates, and compare full_object.updated_ts if order matters.
  • To verify that a request came from AWS, check its signature using the Signature and SigningCertURL fields. See Verifying the signatures of Amazon SNS messages.

Sample notification (SNS envelope)

POST /webhooks/sisu HTTP/1.1
x-amz-sns-message-type: Notification
x-amz-sns-message-id: 7d1541ca-bde6-5be1-b2bb-228b41f641a7
x-amz-sns-topic-arn: arn:aws:sns:us-west-2:033563632468:sisu-webhook-886
x-amz-sns-subscription-arn: arn:aws:sns:us-west-2:033563632468:sisu-webhook-886:b9a58780-ca86-4206-b22e-cd0456a22c52
Content-Type: text/plain; charset=UTF-8
User-Agent: Amazon Simple Notification Service Agent

{
  "Type": "Notification",
  "MessageId": "7d1541ca-bde6-5be1-b2bb-228b41f641a7",
  "TopicArn": "arn:aws:sns:us-west-2:033563632468:sisu-webhook-886",
  "Message": "{\"bulk_guid\": \"519e9a9d-fc8a-4463-b4b7-4095b3357556\", \"acs_id\": 168, \"model\": \"Client\", \"action\": \"update\", ...}",
  "Timestamp": "2026-10-08T20:15:12.296Z",
  "SignatureVersion": "1",
  "Signature": "EXAMPLE...",
  "SigningCertURL": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-....pem",
  "UnsubscribeURL": "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=..."
}

When the payload isn't wrapped in an envelope, the request body is the decoded value of Message, shown in full in the next section.

Payload reference

Top-level fields

FieldTypeDescription
bulk_guidstringIdentifies the change. Every field changed in a single save shares one bulk_guid.
acs_idinteger or nullID of the trigger that produced the event.
modelstringThe record type: one of the model values in Event types.
actionstringinsert, update, delete or archive.
team_idintegerThe Sisu team the record belongs to.
organization_idintegerOnly present on events delivered to an organization-level Topic.
data_objectsarrayThe changes in this event. Currently there's always exactly one element, but iterate over it as an array.

data_objects[]

FieldTypeDescription
previous_valuesobjectEach changed field's value before the change. On insert, every field is null.
updated_valuesobjectEach changed field's new value. Only fields that changed are included.
required_valuesobjectThe record's primary key, such as {"client_id": 1812799}. Always present, whether or not it changed.
object_dataobjectDescribes the record and the change. See below.

object_data

FieldTypeDescription
modelstringSame as the top-level model.
tablestringSisu's name for the record type, such as client or agent.
actionstringSame as the top-level action.
pkeystringThe name of the primary key field, such as client_id.
guidintegerThe primary key value of the changed record.
change_dataobjectWho made the change and where. See below.
full_objectobjectOnly present when Include Full Object is enabled. The record's current field values, with this change applied. Fields whose value is empty (null, "", 0 or false) are left out, so treat a missing key as empty. Not present when the record no longer exists, which can happen on delete.
tagsobjectClient events only. The transaction's tags, keyed by tag ID: {"412": {"name": "VIP", "color": "#1F6FEB", "font_color": "#FFFFFF"}}. Empty ({}) when there are no tags.
agent_recordobjectClient events only, when the transaction has an agent. Contains agent_id, first_name, last_name, full_name, email and mobile_phone.

change_data

FieldTypeDescription
changed_by_agent_idintegerThe Sisu user who made the change, or on whose behalf an API call made it. Absent or 0 when Sisu automation made the change.
typestringWho made the change. Always present. See Change source.
clientstringThe browser user agent, when the change was made from a browser.
data_fromstringA short description of where in Sisu the change originated. It's informational only and may change at any time, so don't build logic on it.

Change source

change_data.type tells you whether a person made the change or an integration did. Use it, for example, to skip events your own integration caused so you don't sync them back.

typeMeaning
wA user signed in to Sisu made the change, on the web or in the mobile app.
aAn API call made the change, using an API key or an OAuth app. This includes your own integration's calls.
iAn API call that identified its integration with the integration-name request header. Treat it the same as a.
uSisu made the change automatically, for example a scheduled job or an automation rule.
erAn e-signature request completed and updated the record.

New codes may be added, so handle unrecognized values gracefully.

Dates and times

By default, dates and times are sent in RFC 1123 format, for example "Fri, 09 Oct 2026 00:00:00 GMT". In this format, seconds are always 00.

When Use ISO date/time format is enabled, they're sent in ISO 8601 format:

  • Date fields (names ending in _dt, such as closed_dt and lead_dt) are calendar dates, such as "2026-08-18". Some date fields are sent with a midnight time, such as "2026-10-09T00:00:00". In either case, use only the date part.
  • Timestamp fields (names ending in _ts, such as created_ts and updated_ts) include fractional seconds and have no UTC offset, such as "2026-10-08T20:15:11.954492". Interpret them as UTC.

Transaction fields

Client events use Sisu's field names as keys. These are the most commonly used, with the labels a real estate team sees in Sisu:

FieldSisu labelNotes
client_idThe transaction's ID.
type_idTransaction Types is a seller (listing) and b is a buyer.
statusN is active and D is archived. An archived transaction is sent with action archive.
pipeline_statusThe transaction's stage in words, such as Pending or Closed.
client_stage_idTransaction StageThe ID of one of the team's stages.
first_name, last_name, full_nameFirst Name, Last NameThe client's name. full_name is only in full_object.
email, mobile_phoneContact Email, Mobile Phone NumberThe client's contact details.
agent_idThe transaction's agent. object_data.agent_record has the agent's name and contact details.
tc_agent_idTCThe transaction coordinator.
address_1, address_2Address Line 1, Address Line 2The property's street address.
city, state, postal_code, countyCity, State, Postal Code, Countystate is a two-letter code.
mls_id, mls_statusMLS ID, MLS Status
property_type, property_sqft, year_builtProperty Type, Property Square Footage, Year Built
listing_amtListing AmountThe list price.
listing_dtMLS Live Date
target_list_dtTarget List Date
listing_agreement_end_dtListing Expiration Date
trans_amtTransaction AmountThe sale price.
lead_type_idLead SourceThe ID of one of the team's lead sources.
lead_dt, appt_set_dt, appt_dtLead Date, 1st Time Appt Set, 1st Time Appt Met
signed_dt, uc_dt, closed_dt, forecasted_closed_dtSigned Date, Under Contract Date, Closed (Settlement) Date, Forecasted Closed Date
gross_commission_amt, gci_pctGCI, GCI %
customThe team's custom field values. See Custom field values.

Labels differ between markets (a mortgage team sees trans_amt as "Loan Amount", for example), and every team has its own custom fields. For the complete list of fields as a specific team sees them, including custom fields and their options, call Get Team Fields (GET /v1/team/fields) with that team's API credentials. Each entry's field_name is the key used in webhook payloads.

Custom field values

A transaction's custom field values live in the custom object, keyed by the custom field's field_name. In previous_values and updated_values, custom contains only the custom fields that changed:

"previous_values": { "custom": { "referral_partner": null } },
"updated_values":  { "custom": { "referral_partner": "Jane Doe" } }

custom can appear as an empty object ({}) alongside other changes. That means no custom field value changed, and you can ignore it.

Sample payloads

Client/Transaction insert

This sample has Include Full Object and Use ISO date/time format enabled. previous_values has the same keys as updated_values, mostly null; it's abridged here.

{
  "bulk_guid": "3c0f1e7a-2b9d-4f61-9a55-0d6b8e4c2a17",
  "acs_id": 168,
  "model": "Client",
  "action": "insert",
  "team_id": 886,
  "data_objects": [
    {
      "previous_values": {
        "client_id": null,
        "type_id": null,
        "address_1": null,
        "listing_amt": null
      },
      "updated_values": {
        "client_id": 1812800,
        "type_id": "s",
        "first_name": "Jordan",
        "last_name": "Seller",
        "email": "[email protected]",
        "mobile_phone": "8015550142",
        "agent_id": 3384,
        "status": "N",
        "pipeline_status": "Signed",
        "client_stage_id": 35460,
        "address_1": "123 Main St",
        "city": "Salt Lake City",
        "state": "UT",
        "postal_code": "84101",
        "county": "Salt Lake",
        "property_type": "Single Family",
        "listing_amt": 625000.0,
        "target_list_dt": "2026-10-20",
        "listing_agreement_end_dt": "2027-04-20",
        "signed_dt": "2026-10-08",
        "lead_type_id": 14,
        "custom": {
          "listing_photographer": "Booked"
        }
      },
      "required_values": {
        "client_id": 1812800
      },
      "object_data": {
        "model": "Client",
        "table": "client",
        "action": "insert",
        "pkey": "client_id",
        "guid": 1812800,
        "change_data": {
          "type": "w",
          "client": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
          "changed_by_agent_id": 3384
        },
        "tags": {},
        "agent_record": {
          "agent_id": 3384,
          "first_name": "Alex",
          "last_name": "Agent",
          "full_name": "Alex Agent",
          "email": "[email protected]",
          "mobile_phone": "8015550100"
        },
        "full_object": {
          "client_id": 1812800,
          "type_id": "s",
          "first_name": "Jordan",
          "last_name": "Seller",
          "full_name": "Jordan Seller",
          "email": "[email protected]",
          "mobile_phone": "8015550142",
          "agent_id": 3384,
          "status": "N",
          "pipeline_status": "Signed",
          "client_stage_id": 35460,
          "address_1": "123 Main St",
          "city": "Salt Lake City",
          "state": "UT",
          "postal_code": "84101",
          "county": "Salt Lake",
          "property_type": "Single Family",
          "listing_amt": 625000.0,
          "target_list_dt": "2026-10-20",
          "listing_agreement_end_dt": "2027-04-20",
          "signed_dt": "2026-10-08",
          "lead_type_id": 14,
          "custom": {
            "listing_photographer": "Booked"
          },
          "created_ts": "2026-10-08T21:04:17.120334",
          "updated_ts": "2026-10-08T21:04:17.120334"
        }
      }
    }
  ]
}

Client/Transaction update

This sample has Include Full Object and Use ISO date/time format enabled. The transaction moved to a new stage and gained a closing date.

{
  "bulk_guid": "519e9a9d-fc8a-4463-b4b7-4095b3357556",
  "acs_id": 168,
  "model": "Client",
  "action": "update",
  "team_id": 886,
  "data_objects": [
    {
      "previous_values": {
        "closed_dt": null,
        "custom": {},
        "client_stage_id": 39481
      },
      "updated_values": {
        "closed_dt": "2026-10-09T00:00:00",
        "custom": {},
        "client_stage_id": 35462
      },
      "required_values": {
        "client_id": 1812799
      },
      "object_data": {
        "model": "Client",
        "table": "client",
        "action": "update",
        "pkey": "client_id",
        "guid": 1812799,
        "change_data": {
          "type": "w",
          "client": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
          "data_from": "transactions.pwa_transaction - client.set",
          "changed_by_agent_id": 3384
        },
        "tags": {},
        "agent_record": {
          "agent_id": 3384,
          "first_name": "Alex",
          "last_name": "Agent",
          "full_name": "Alex Agent",
          "email": "[email protected]",
          "mobile_phone": "8015550100"
        },
        "full_object": {
          "client_id": 1812799,
          "contact_id": 35651368,
          "agent_id": 3384,
          "tc_agent_id": 146532,
          "related_team_id": 886,
          "type_id": "s",
          "first_name": "Test",
          "last_name": "Client",
          "full_name": "Test Client",
          "email": "[email protected]",
          "mobile_phone": "8015550199",
          "status": "N",
          "pipeline_status": "Pending",
          "client_stage_id": 35462,
          "board_sort_order": 26,
          "lead_type_id": 14,
          "lead_dt": "2026-08-18",
          "appt_set_dt": "2026-06-03",
          "appt_dt": "2026-06-12",
          "signed_dt": "2026-08-07",
          "uc_dt": "2026-08-18",
          "closed_dt": "2026-10-09T00:00:00",
          "financing": "Conventional",
          "orig_trans_amt": 850000.0,
          "trans_amt": 850000.0,
          "gci_pct": 3.0,
          "gross_commission_amt": 25500.0,
          "commission_amt": 12750.0,
          "split_pct": 50.0,
          "team_split_pct": 50.0,
          "created_ts": "2026-08-18T13:43:48.763270",
          "updated_ts": "2026-10-08T20:15:11.954492"
        }
      }
    }
  ]
}

Agent/User insert

This sample has Include Full Object enabled and uses the default date format. previous_values, updated_values and full_object are abridged.

{
  "bulk_guid": "b7ebfb48-0078-4eb1-8fa5-767f55646b37",
  "acs_id": 143,
  "model": "Agent",
  "action": "insert",
  "team_id": 886,
  "data_objects": [
    {
      "previous_values": {
        "agent_id": null,
        "first_name": null,
        "last_name": null,
        "mobile_phone": null,
        "email": null,
        "status": null,
        "start_dt": null
      },
      "updated_values": {
        "agent_id": 76395,
        "first_name": "New",
        "last_name": "User",
        "mobile_phone": "8015551234",
        "email": "[email protected]",
        "status": "N",
        "start_dt": "Thu, 13 Jan 2022 00:00:00 GMT"
      },
      "required_values": {
        "agent_id": 76395
      },
      "object_data": {
        "model": "Agent",
        "table": "agent",
        "action": "insert",
        "pkey": "agent_id",
        "guid": 76395,
        "change_data": {
          "type": "w",
          "client": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
          "changed_by_agent_id": 3384
        },
        "full_object": {
          "agent_id": 76395,
          "first_name": "New",
          "last_name": "User",
          "full_name": "New User",
          "email": "[email protected]",
          "archived_email": "[email protected]",
          "mobile_phone": "8015551234",
          "status": "N",
          "start_dt": "Thu, 13 Jan 2022 00:00:00 GMT",
          "created_ts": "Thu, 13 Jan 2022 17:33:00 GMT"
        }
      }
    }
  ]
}

Agent/User update

Only the fields that changed appear in previous_values and updated_values. This sample has Include Full Object disabled.

{
  "bulk_guid": "4bd24f33-7fde-436d-a926-08d6849d81de",
  "acs_id": 143,
  "model": "Agent",
  "action": "update",
  "team_id": 886,
  "data_objects": [
    {
      "previous_values": {
        "first_name": "New",
        "last_name": "User"
      },
      "updated_values": {
        "first_name": "NewUPDATE",
        "last_name": "UserUPDATE"
      },
      "required_values": {
        "agent_id": 76395
      },
      "object_data": {
        "model": "Agent",
        "table": "agent",
        "action": "update",
        "pkey": "agent_id",
        "guid": 76395,
        "change_data": {
          "type": "w",
          "client": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
          "changed_by_agent_id": 3384
        }
      }
    }
  ]
}

Agent/User archive

Archiving a user sets status from N to D, and is sent with action archive.

{
  "bulk_guid": "e3cf542a-6652-4436-92e1-56961a1fd9a7",
  "acs_id": 143,
  "model": "Agent",
  "action": "archive",
  "team_id": 886,
  "data_objects": [
    {
      "previous_values": {
        "status": "N"
      },
      "updated_values": {
        "status": "D"
      },
      "required_values": {
        "agent_id": 76395
      },
      "object_data": {
        "model": "Agent",
        "table": "agent",
        "action": "archive",
        "pkey": "agent_id",
        "guid": 76395,
        "change_data": {
          "type": "u"
        }
      }
    }
  ]
}

Custom Field update (option added)

When a custom field's options change, previous_values.options and updated_values.options contain the field's complete option list before and after the change, keyed by option ID. An option's status is N when active and D when removed. This sample has Include Full Object disabled and uses the default date format.

{
  "bulk_guid": "0f3b6c1e-55a2-4d1b-9a3e-6d1f2b7c9e40",
  "acs_id": 917,
  "model": "TeamCustomClientField",
  "action": "update",
  "team_id": 886,
  "data_objects": [
    {
      "previous_values": {
        "options": {
          "0": { "name": "Red", "status": "N", "is_enabled": "Y" }
        },
        "modified_ts": "Mon, 05 Oct 2026 16:42:00 GMT"
      },
      "updated_values": {
        "options": {
          "0": { "name": "Red", "status": "N", "is_enabled": "Y" },
          "1": { "name": "Blue", "status": "N", "is_enabled": "Y" }
        },
        "modified_ts": "Thu, 08 Oct 2026 20:10:00 GMT"
      },
      "required_values": {
        "team_custom_client_field_id": 5521
      },
      "object_data": {
        "model": "TeamCustomClientField",
        "table": "team_custom_client_field",
        "action": "update",
        "pkey": "team_custom_client_field_id",
        "guid": 5521,
        "change_data": {
          "type": "w",
          "data_from": "TeamCustomClientField.set_options",
          "changed_by_agent_id": 3384
        }
      }
    }
  ]
}

A custom field's team_custom_client_field_id never changes, so use it to match update and delete events to a field you already know about. With Include Full Object enabled, full_object.field_name gives the field's name on insert and update events. That name is the key used in a transaction's custom object.


Did this page help you?