# 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](https://docs.uberall.com/apis/swagger/webhooks) - Previously, they were set by the [PUT /api/sales-partners/$id](https://docs.uberall.com/apis/swagger/sales-partners/put_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: ```bash 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`) ```bash 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 | Type | Description | | --- | --- | | LISTING_SYNC_CHECK | a listing was sync checked, including possible status listing changes | | LISTING_LINK_CHANGE | the url of a listing has changed | | LISTING_UPDATE | listing's data was attempted to be submitted to the directory | | 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. | | LISTING_DATAPOINT_CHECK | New [datapoint(s)](https://docs.uberall.com/apis/swagger/data-points) for a listing have been found or changes to existing datapoints have occurred | | LISTING_DATAPOINT_INVALID | a listing datapoint has been deleted from the directory so we have invalidated it, too | | DIRECTORY_BUSINESS_PAGE_DATA_POINT_CHECK | new [datapoint(s)](https://docs.uberall.com/apis/swagger/data-points) for a brand page been found | | DIRECTORY_BUSINESS_PAGE_DATA_POINT_INVALID | a brand datapoint has been deleted from the directory so we have invalidated it, too | | LOCATION_CREATED | a location has been created | | LOCATION_STATUS_CHANGED | a location's status has changed | | LOCATION_PROFILE_CHANGED | a location has been updated by a user | | BUSINESS_PRODUCT_PLAN_CHANGED | a business (and its subsequent locations) has changed product plans | | BUSINESS_CREATED | a 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. ```json { "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: | Field | Description | Notes | | --- | --- | --- | | `id` | Unique event ID | For deduplication | | `time` | Event timestamp | | | `type` | Event type | See Event Types table | | `listing` | Related listing ID | | | `location` | Related location ID | For fetching updated data | | `business` | Related business ID | | | `salesPartnerIdentifier` | Partner account identifier | For 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: ```javascript 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 details summary strong 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. ```json { "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" } } ``` | Field | Type | Description | | --- | --- | --- | | directoryType | string | The directory this listing belongs to | | claimStatusChanged | boolean | Indicating whether the listing's claim status has changed | | googleStatusChanged | boolean | Indicating whether the listing's Google connection status has changed | | flowStatusChanged | boolean | Indicating whether the listing's flow status changed | | syncStatusChanged | boolean | Indicating whether the listing's sync status changed | | fromListingClaimStatus | ListingClaimStatus | The previous ClaimStatus | | fromListingFlowStatus | FlowStatus | The previous FlowStatus | | fromListingSyncStatus | SyncStatus | The previous SyncStatus | | toListingClaimStatus | ListingClaimStatus | The new ClaimStatus | | toListingFlowStatus | FlowStatus | The new FlowStatus | | toListingSyncStatus | SyncStatus | The new SyncStatus | details summary strong 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. ```json { "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" } } ``` | Field | Type | Description | | --- | --- | --- | | directoryType | string | The directory this listing belongs to | | claimStatusChanged | boolean | Indicating whether the claim status changed | | flowStatusChanged | boolean | Indicating whether the flow status changed | | syncStatusChanged | boolean | Indicating whether the sync status changed | | fromListingClaimStatus | ListingClaimStatus | The previous ClaimStatus | | fromListingFlowStatus | FlowStatus | The previous FlowStatus | | fromListingSyncStatus | SyncStatus | The previous SyncStatus | | toListingClaimStatus | ListingClaimStatus | The new ClaimStatus | | toListingFlowStatus | FlowStatus | The new FlowStatus | | toListingSyncStatus | SyncStatus | The new SyncStatus | | user | string | The user that changed the status | details summary strong 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. ```json { "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" } ``` | Field | Type | Description | | --- | --- | --- | | fromListingUrl | string | The old listing url | | fromListingId | string | The old external id of the listing | | toListingUrl | string | The new listing url | | toListingId | string | The new external id of the listing | details summary strong 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. ```json { "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" } ``` | Field | Type | Description | | --- | --- | --- | | directoryType | string | The directory this listing belongs to | | newPhotosFound | integer | The number of new photos found | | newReviewsFound | integer | The number of new reviews found | | newCheckinsFound | integer | The number of new checkins found | | newConversationsFound | integer | The number of new conversations found | | newCommentsFound | integer | The number of new comments found | | newQuestionsFound | integer | The number of new questions found | | newDatapointsFound | boolean | Indicates if any new data points have been fond | | newDataPointIds | array | Contains the ids of the newly found data points | | updatedDataPointIds | array | Contains the ids of any existing and modified data points (e.g. customer review) | details summary strong 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. ```json { "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=" } ``` | Field | Type | Description | | --- | --- | --- | | deletedDataPointIds | Array | The ids of the invalidated data point replies (e.g. owner reply to a customer review) | | invalidDataPointIds | Array | The ids of the invalidated data points (e.g. customer review) | details summary strong DIRECTORY_BUSINESS_PAGE_DATA_POINT_CHECK Indicates that a new brand data point has been found, e.g. review on a Facebook brand page ```json { "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=" } ``` | Field | Type | Description | | --- | --- | --- | | directoryBusinessPage | Integer | The id of the business page associated with the brand data point | | newDataPointIds | Array | The ids of the newly found brand data points | | updatedDataPointIds | Array | contains the ids of any existing and modified data points (e.g. customer review) | details summary strong 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. ```json { "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=" } ``` | Field | Type | Description | | --- | --- | --- | | directoryBusinessPage | Integer | The id of the business page associated with the brand data point | | deletedDataPointIds | Array | The ids of the invalidated data point replies (e.g. owner reply to a customer review) | | invalidDataPointIds | Array | The ids of the invalidated data points (e.g. customer review) | details summary strong LISTING_UPDATE Indicates that we tried to submit the listing to the directory. ```json { "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" } ``` | Field | Type | Description | | --- | --- | --- | | directoryType | string | The directory's listing type which we tried to submit | | updateResult | string | - `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 | | updateStatus | string | `null` 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 | details summary strong LOCATION_CREATED Indicates that a new location has been created in your account. ```json { "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" } ``` | Field | Type | Description | | --- | --- | --- | | source | string | one among [API, CHECKOUT, CSV_UPLOAD, DEMO_JOB, LOCATION_DATA_DOWNLOAD] | details summary strong LOCATION_STATUS_CHANGED Indicates that the status of a location changed. ```json { "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" } ``` | Field | Type | Description | | --- | --- | --- | | from | LocationStatus | the previous LocationStatus | | to | LocationStatus | the new LocationStatus | | user | string | the user who changed the status (if it was a manual change) | details summary strong LOCATION_PROFILE_CHANGED Indicates that a user changed data of a location. ```json { "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" } ``` | Field | Type | Description | | --- | --- | --- | | user | string | the user that changed the data | details summary strong 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 ```json { "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" } ``` | Field | Type | Description | | --- | --- | --- | | newProductPlanId | Long | the id of newly assigned ProductPlan | | newProductPlanName | String | the name of the newly assigned ProductPlan | | newProductPlanPrice | Integer | the price (in Cent) of newly assigned ProductPlan | | oldProductPlanId | Long | the id of the previously assigned ProductPlan | | oldProductPlanName | String | the name of the previously assigned ProductPlan | | oldProductPlanPrice | Integer | the price (in Cent) of the previously assigned ProductPlan | | changedBy | String | Either one of the following, indicating who triggered the change:1. user email2. “PRIVATE_TOKEN”3. “SYSTEM” | details summary strong BUSINESS_CREATED Indicates that a new Business has been created in the partner account ```json { "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” } ``` | Field | Type | Description | | --- | --- | --- | | changedBy | String | Either one of the following, indicating who triggered the change:1. user email2. “PRIVATE_TOKEN”3. “SYSTEM” |