Last updated

Webhooks

January 2024

Design & API changes

  • Webhooks are no longer limited to only 1 URL for the entire configuration. Instead, every webhook can have its specific destination URL.
  • Webhooks now have their dedicated API endpoint: /api/sales-partners/webhooks
    • Previously, they were set by the PUT /api/sales-partners/$id endpoint and passing the pushUrl and subscribedWebhookEventTypes which is no longer supported.

New Webhooks

We have introduced new webhooks explained in the Event types definitions section.

  • LISTING_DATAPOINT_INVALID
  • BRAND_DATAPOINT_CHECK
  • DIRECTORY_BUSINESS_PAGE_DATA_POINT_INVALID
  • LOCATION_DATA_SUGGESTION

Existing webhook changes

We have added parameters to streamline the handling of data points.

LISTING_DATAPOINT_CHECK – when you listen for new data points, now you also receive which are the new datapoints so that they can be directly queried after

  • The boolean field newDatapointsFound indicates if any new data point has been found
  • The array newDataPointIds lists the ids of each newly scraped datapoint. The same logic is applicable for BRAND_DATAPOINT_CHECK
  • The array updatedDataPointIds will contain information for existing data points that have been updated, e.g. the end user has modified a customer review.

What is the event service? What are webhooks?

Webhooks are automated messages sent from apps when something happens. The platform's Event Service (Webhooks) allows enables real-time communication between our platform and other applications. It works by sending HTTP POST requests to a provided URL for subscribed events, such as changes in location information, updates to business listings, or modifications to social media interactions (e.g. reviews). This seamless integration allows you to stay informed about dynamic changes and facilitates the transfer of real-time data.

It can help you get real-time pings when and only a change happens

Setting up the service

The service can be set up in two ways:

Set up via the platform's interface

This is the most straightforward way :

  1. Log in with an Admin user
  2. Navigate to Org settings
  3. Access Webhooks tab
  4. Add target URLs per webhook and see available webhooks

API set up

Webhooks can be also set up by the API.

Supported webhook types

We offer various webhooks explained further below. Based on your use cases, you may decide to consume one or multiple webhooks.

To understand which are the currently supported event types (webhooks) you can query the following endpoint:

curl --location 'https://$yourdomain.com/api/sales-partners/subscribable-event-types' \
--header 'privateKey: $privateKey'

Subscribe to a Webhook

To set up a new webhook you can run the following query. We allow sending the same webhook to multiple destinations (pushUrl)

curl --location 'https://$yourdomain.com/api/salespartners/webhooks' \
--header 'privateKey: $privateKey' \
--data '{
    "pushUrl": "https://webhook.site/e84ba9cc-d56f-4e30-9607-cd3ddc65952f",
    "type": "LISTING_SYNC_CHECK"
}'

Event types definitions

TypeDescription
LISTING_SYNC_CHECKa listing was sync checked, including possible status listing changes
LISTING_LINK_CHANGEthe url of a listing has changed
LISTING_UPDATElisting's data was attempted to be submitted to the directory
LISTING_STATUS_CHANGEIndicates that the status of a listing was changed manually. This happens rarely and often is the result of a manual check by our Operations or Engineering teams.
LISTING_DATAPOINT_CHECKNew datapoint(s) for a listing have been found or changes to existing datapoints have occurred
LISTING_DATAPOINT_INVALIDa listing datapoint has been deleted from the directory so we have invalidated it, too
DIRECTORY_BUSINESS_PAGE_DATA_POINT_CHECKnew datapoint(s) for a brand page been found
DIRECTORY_BUSINESS_PAGE_DATA_POINT_INVALIDa brand datapoint has been deleted from the directory so we have invalidated it, too
LOCATION_CREATEDa location has been created
LOCATION_STATUS_CHANGEDa location's status has changed
LOCATION_PROFILE_CHANGEDa location has been updated by a user
BUSINESS_PRODUCT_PLAN_CHANGEDa business (and its subsequent locations) has changed product plans
BUSINESS_CREATEDa business has been created

The target URL and the subscribed events can be changed during runtime. In case the transmission of an event fails, the system will retry a few times and then discard the event.

What a payload looks like

Here's the basic structure of a payload. More information about the different event types and loads can be found in the Field Descriptions and Events.

{
  "event": {
    "comment": "Location profile has been updated",
    "id": 2926113683,
    "location": 636605,
    "time": "2018-11-05T17:35:07.000+01:00",
    "type": "LOCATION_PROFILE_CHANGED",
    "user": "user@test.com"
  },
  "signature": "X"
}

All JSON payloads will always have the event and signature fields.

Field Description

Event

The event represents the change that has happened in the platform and it serves as the actual payload and varies based on the event type. Key fields include but are not limited to:

FieldDescriptionNotes
idUnique event IDFor deduplication
timeEvent timestamp
typeEvent typeSee Event Types table
listingRelated listing ID
locationRelated location IDFor fetching updated data
businessRelated business ID
salesPartnerIdentifierPartner account identifierFor multi-account management

All other fields are different per event type and described further down.

Signature Verification

The signature is calculated by casting the event field to a String and then HMAC_SHA256 encrypted using the sales partner's private key and BASE64 encode the result. The events fields need to be ordered alphabetically before decoding!

Pseudo Code:

let json = {"asd": "qwe", "foo": "bar"};
let str = json.encodeAsJson();
assert str === '{"asd": "qwe", "foo": "bar"}'

let privateKey = "this-is-a-secret-key"
let encrypted = hmacShaEncrypt(privateKey, str)
let signature = encrypted.encodeAsBase64()
assert signature === 'UAQuAderRxKW8nMPhyU8oVhS6U8PgG8d/I03lcbNAG4='

Events

Below is the list of the supported events. We continue

LISTING_SYNC_CHECK

Indicates that any of the three main listing statuses (Claim, Flow, Sync) changed. It includes information about the lifecycle of the listing and the Google status of the listing.

{
  "signature": "X",
  "event": {
      "claimStatusChanged": false,
      "googleStatusChanged": false,
      "directoryType": "ABCLOCAL",
      "flowStatusChanged": true,
      "fromListingClaimStatus": "CLAIMED_BY_US",
      "fromListingFlowStatus": "ALL_INFORMATION_SUBMITTED",
      "fromListingSyncStatus": "NOT_IN_SYNC",
      "id": 213581003,
      "listing": 332874,
      "location": 34838,
      "syncStatusChanged": true,
      "time": "2016-05-03T10:42:06.000+02:00",
      "toListingClaimStatus": "CLAIMED_BY_US",
      "toListingFlowStatus": "NO_ACTION_NEEDED",
      "toListingSyncStatus": "IN_SYNC",
      "type": "LISTING_SYNC_CHECK"
  }
}
FieldTypeDescription
directoryTypestringThe directory this listing belongs to
claimStatusChangedbooleanIndicating whether the listing's claim status has changed
googleStatusChangedbooleanIndicating whether the listing's Google connection status has changed
flowStatusChangedbooleanIndicating whether the listing's flow status changed
syncStatusChangedbooleanIndicating whether the listing's sync status changed
fromListingClaimStatusListingClaimStatusThe previous ClaimStatus
fromListingFlowStatusFlowStatusThe previous FlowStatus
fromListingSyncStatusSyncStatusThe previous SyncStatus
toListingClaimStatusListingClaimStatusThe new ClaimStatus
toListingFlowStatusFlowStatusThe new FlowStatus
toListingSyncStatusSyncStatusThe new SyncStatus
LISTING_STATUS_CHANGE

Indicates that the status of a listing was changed manually. This happens rarely and often is the result of a manual check by our Operations or Engineering teams.

{
  "signature": "X",
  "event": {
      "claimStatusChanged": false,
      "directoryType": "ABCLOCAL",
      "flowStatusChanged": true,
      "fromListingClaimStatus": "CLAIMED_BY_US",
      "fromListingFlowStatus": "NO_ACTION_NEEDED",
      "fromListingSyncStatus": "NOT_IN_SYNC",
      "id": 213581003,
      "listing": 332874,
      "location": 34838,
      "syncStatusChanged": false,
      "time": "2016-05-03T10:42:06.000+02:00",
      "toListingClaimStatus": "CLAIMED_BY_US",
      "toListingFlowStatus": "SUBMISSION_NEEDED",
      "toListingSyncStatus": "NOT_IN_SYNC",
      "type": "LISTING_STATUS_CHANGE",
      "user": "user@example.com"
  }
}
FieldTypeDescription
directoryTypestringThe directory this listing belongs to
claimStatusChangedbooleanIndicating whether the claim status changed
flowStatusChangedbooleanIndicating whether the flow status changed
syncStatusChangedbooleanIndicating whether the sync status changed
fromListingClaimStatusListingClaimStatusThe previous ClaimStatus
fromListingFlowStatusFlowStatusThe previous FlowStatus
fromListingSyncStatusSyncStatusThe previous SyncStatus
toListingClaimStatusListingClaimStatusThe new ClaimStatus
toListingFlowStatusFlowStatusThe new FlowStatus
toListingSyncStatusSyncStatusThe new SyncStatus
userstringThe user that changed the status
LISTING_LINK_CHANGE

The platform listing is now pointing to a different directory listing. This can happen either manually or automatically, by detecting a better listing on the directory side than the one previously linked.

{
  "event": {
      "business": 1234,
      "comment": "Listing link changed",
      "fromListingId": "456",
      "fromListingUrl": "http://old-listing.url/456",
      "id": 5549734563,
      "listing": 2345,
      "location": 3456,
      "time": "2019-11-18T14:12:36.000+01:00",
      "toListingId": "789",
      "toListingUrl": "http://new-listing.url/789",
      "type": "LISTING_LINK_CHANGE"
  },
  "signature": "X"
}
FieldTypeDescription
fromListingUrlstringThe old listing url
fromListingIdstringThe old external id of the listing
toListingUrlstringThe new listing url
toListingIdstringThe new external id of the listing
LISTING_DATAPOINT_CHECK

Indicates that while doing a check for listing datapoints (i.e. customer feedback such as reviews, photos, etc.), we found new ones.

{
  "event": {
      "business": 1420951,
      "directoryType": "JUDYS_BOOK"
      "id": 213580990,
      "listing": 332874,
      "location": 34838,
      "newCheckinsFound": 1,
      "newCommentsFound": 1,
      "newConversationsFound": 1,
      "newDatapointsFound": true,
      "newPhotosFound": 1,
      "newQuestionsFound": 1,
      "newReviewsFound": 1,
      "newDataPointIds": [
      250976288,
      250976289
      ],
      "time": "2016-05-03T10:19:14.000+02:00",
      "type": "LISTING_DATAPOINT_CHECK"
  },
  "signature": "X"
}
FieldTypeDescription
directoryTypestringThe directory this listing belongs to
newPhotosFoundintegerThe number of new photos found
newReviewsFoundintegerThe number of new reviews found
newCheckinsFoundintegerThe number of new checkins found
newConversationsFoundintegerThe number of new conversations found
newCommentsFoundintegerThe number of new comments found
newQuestionsFoundintegerThe number of new questions found
newDatapointsFoundbooleanIndicates if any new data points have been fond
newDataPointIdsarrayContains the ids of the newly found data points
updatedDataPointIdsarrayContains the ids of any existing and modified data points (e.g. customer review)
LISTING_DATAPOINT_INVALID

Indicates that a listing's datapoint has been deleted or invalidated on the directory, so we invalidate it on our side. You can delete or invalidate them on your side too. It's unlikely that a datapoint will be reinstated by the directory.

{
  "event": {
      "business": 1107630,
      "deletedDataPointIds": [],
      "directoryType": "YELP_API",
      "id": "99",
      "invalidDataPointIds": [
          434424555
      ],
      "listing": 119934719,
      "location": 4154762,
      "time": "2023-12-20T19:00:34.508+01:00",
      "type": "LISTING_DATAPOINT_INVALID"
  },
  "signature": "YIfeSTln/p2JaPTY3WhVSsFRvy3NgtRhCFzGI/Fc+60="
}
FieldTypeDescription
deletedDataPointIdsArrayThe ids of the invalidated data point replies (e.g. owner reply to a customer review)
invalidDataPointIdsArrayThe ids of the invalidated data points (e.g. customer review)
DIRECTORY_BUSINESS_PAGE_DATA_POINT_CHECK

Indicates that a new brand data point has been found, e.g. review on a Facebook brand page

{
  "event": {
      "business": 244397,
      "directoryBusinessPage": 1220,
      "directoryType": "FACEBOOK",
      "id": 4,
      "newDataPointIds": [
          289,
          299
      ],
      "newDatapointsFound": true,
      "time": "2023-12-21T12:03:31.358+01:00",
      "type": "DIRECTORY_BUSINESS_PAGE_DATA_POINT_CHECK",
      "updatedDataPointIds": [
          79,
          253
      ]
  },
  "signature": "9kTmfuFg9T5LDD59ZodiyOFtoLSItG737ZRL2fBD7O4="
}
FieldTypeDescription
directoryBusinessPageIntegerThe id of the business page associated with the brand data point
newDataPointIdsArrayThe ids of the newly found brand data points
updatedDataPointIdsArraycontains the ids of any existing and modified data points (e.g. customer review)
DIRECTORY_BUSINESS_PAGE_DATA_POINT_INVALID

Indicates that a brand's datapoint has been deleted or invalidated on the directory, so we invalidate it on our side. You can delete or invalidate them on your side too. It's unlikely that a datapoint will be reinstated by the directory.

{
"event": {
  "business": 909288,
  "deletedDataPointIds": [],
  "directoryBusinessPage": 173,
  "directoryType": "FACEBOOK",
  "id": 7,
  "invalidDataPointIds": [
    4737279
  ],
  "time": "2024-01-02T17:17:49.664+01:00",
  "type": "DIRECTORY_BUSINESS_PAGE_DATA_POINT_INVALID"
},
"signature": "/GHkwuBIxYadsaWPq1V8vztXVuSlf1txDSxZuBAH1UQ="
}
FieldTypeDescription
directoryBusinessPageIntegerThe id of the business page associated with the brand data point
deletedDataPointIdsArrayThe ids of the invalidated data point replies (e.g. owner reply to a customer review)
invalidDataPointIdsArrayThe ids of the invalidated data points (e.g. customer review)
LISTING_UPDATE

Indicates that we tried to submit the listing to the directory.

{
  "event": {
      "business": 7141,
      "directoryType": "BING",
      "id": 17518447719,
      "listing": 7198166,
      "location": 450978,
      "messages": [
      "Listing was updated successfully!"
      ],
      "time": "2021-10-27T16:21:14.000+02:00",
      "type": "LISTING_UPDATE",
      "updateResult": "SUCCESS",
      "updateStatus": null
  },
  "signature": "X"
}
FieldTypeDescription
directoryTypestringThe directory's listing type which we tried to submit
updateResultstring- SUCCESS – listing has been successfully updated
- FAILURE – listing update was rejected by directory
- UNDEFINED – listing data could not be submitted due to some reason outlined in UpdateStatus below
updateStatusstringnull if updateResult = SUCCESS. Else, can be one of:

- MANDATORY_FIELD_MISSING – one or more mandatory fields are missing in the location
- NOT_SUPPORTED – the listing is not supported on that directory (e.g. INSTAGRAM – only listing URLs, no direct submission)
- INVALID_CLAIM_STATUS – e.g. CLAIMED_BY_OTHERS, but we can only update CLAIMED_BY_US
- INVALID_FLOW_STATUS – e.g. NEEDS_REVIEW
- INVALID_SYNC_STATUS – e.g. listing has TECHNICAL_PROBLEMS
- INVALID_LOCATION_STATUS – e.g. location status is NEEDS_REVIEW
- LOCATION_NOT_NORMALIZED – location is not normalized
- DUPLICATED – listing is duplicated; should not be shown as failure (see CylexUpdateService#update)
- LIMIT_REACHED – throttling or directory-imposed limits reached
- ADDRESS_DISPLAY_NOT_SUPPORTED – directory does not support address display
- LOCATION_NOT_SYNCED – the location has not been synced yet
- LOCATION_NOT_CLEANSED – location has not been cleansed; some directories require cleansing before updates
- DELAYED – sync postponed due to directory restrictions
LOCATION_CREATED

Indicates that a new location has been created in your account.

{
  "event": {
      "comment": "A new Location has been created",
      "id": 2926113683,
      "location": 636605,
      "time": "2018-11-05T17:35:07.000+01:00",
      "type": "LOCATION_CREATED",
      "source": "API"
  },
  "signature": "X"
}
FieldTypeDescription
sourcestringone among [API, CHECKOUT, CSV_UPLOAD, DEMO_JOB, LOCATION_DATA_DOWNLOAD]
LOCATION_STATUS_CHANGED

Indicates that the status of a location changed.

{
  "event": {
      "comment": "Checked and activated by a human being.",
      "from": "ACTIVE",
      "id": 213580999,
      "location": 34838,
      "time": "2016-05-03T10:34:43.000+02:00",
      "to": "CANCELLED",
      "type": "LOCATION_STATUS_CHANGED",
      "user": "user@example.com"
  },
  "signature": "X"
}
FieldTypeDescription
fromLocationStatusthe previous LocationStatus
toLocationStatusthe new LocationStatus
userstringthe user who changed the status (if it was a manual change)
LOCATION_PROFILE_CHANGED

Indicates that a user changed data of a location.

{
"event": {
  "comment": "Location profile has been updated",
  "id": 2926113683,
  "location": 636605,
  "time": "2018-11-05T17:35:07.000+01:00",
  "type": "LOCATION_PROFILE_CHANGED",
  "user": "user@example.com"
},
"signature": "X"
}
FieldTypeDescription
userstringthe user that changed the data
BUSINESS_PRODUCT_PLAN_CHANGED

Indicates that a user and API update or our system has changed the ProductPlan of a Business and its assigned Locations

{
  "event": {
      "business": 1,
      "comment": "Business product plan has been changed",
      "id": 5136210330,
      "salesPartnerIdentifier": "identifier",
      "newProductPlanId": 2,
      "newProductPlanName": "My Product Plan 2",
      "newProductPlanPrice": 20,
      "oldProductPlanId": 1,
      "oldProductPlanName": "My Product Plan 1",
      "oldProductPlanPrice": 10,
      "time": "2019-10-09T15:10:01.000+02:00",
      "type": "BUSINESS_PRODUCT_PLAN_CHANGED",
      "changedBy": "user@example.com"
  },
  "signature": "XYZ"
}
FieldTypeDescription
newProductPlanIdLongthe id of newly assigned ProductPlan
newProductPlanNameStringthe name of the newly assigned ProductPlan
newProductPlanPriceIntegerthe price (in Cent) of newly assigned ProductPlan
oldProductPlanIdLongthe id of the previously assigned ProductPlan
oldProductPlanNameStringthe name of the previously assigned ProductPlan
oldProductPlanPriceIntegerthe price (in Cent) of the previously assigned ProductPlan
changedByStringEither one of the following, indicating who triggered the change:
1. user email
2. “PRIVATE_TOKEN”
3. “SYSTEM”
BUSINESS_CREATED

Indicates that a new Business has been created in the partner account

{
  "event": {
      "business": 631498,
      "changedBy": "PRIVATE_TOKEN",
      "comment": "New business has been created",
      "id": 5385813249,
      "salesPartnerIdentifier": "identifier",
      "time": "2019-11-07T16:30:19.000+01:00",
      "type": "BUSINESS_CREATED"
  },
  "signature": "X”
}
FieldTypeDescription
changedByStringEither one of the following, indicating who triggered the change:
1. user email
2. “PRIVATE_TOKEN”
3. “SYSTEM”