Webhooks Guide

Webhooks allow you to know when something happens within Percolate. When an event such as a Campaign is created or a piece of Content publishes, Percolate will send an HTTP request to your application with a payload describing the change that occurred. You can use this information to automate or trigger some secondary action such as sending an email or a Slack notification.

To utilize webhooks, we’ll first need to create a webhook subscription.

Creating a webhook subscription

Describing a webhook subscription

A webhook subscription can be created via the API or the UI. The following fields can be specified when defining a webhook:

  1. name (string, required) - the identifier for the webhook subscription.
  2. target_url (string, required) - this is the URL endpoint of your application to which Percolate will send events. It should be secure and support HTTPS.
  3. message_ttl (number) - this value is given in seconds and can only be between 3600 and 259200 seconds (1 and 72 hours). It lets Percolate’s event delivery system know how long to keep retrying delivery of a given event in case we’re unable to deliver it on the first try. i.e. For a message_ttl of 86400 seconds (24 hours) we will keep retrying delivery for a given event for 24 hours before discarding it.
  4. events (array of objects, required) - this describes the types of events Percolate should send to your subscription. Each object in this array has the following keys:

    object_type (string, required) - this identifies the type of Percolate object e.g Content, Campaign and approval workflow transitions for both Content and Campaign.
    change_type (string, required) - this identifies the type of change on the aforementioned object to listen for and can only be one of create, update or delete.

📘

An event object with an object_type “post” and change_type “update” would mean the subscription should listen for updates to Content within the given scopes.

  1. scopes (array of objects, required) - these are the teams Percolate will listen for events on. They should all belong to the same tenant. Each object in this array has the following keys:

    scope_id (string, required) - this is a license ID identifying the team to listen for events on. e.g. license:1

  2. headers (array of objects) - these are the HTTP headers Percolate will send to your application along with the event payload. These can be standard or custom HTTP headers. Each object in this array has the following keys:

    key (string, required) - HTTP header key e.g. “Authorization”
    value (string, required) - HTTP header value e.g. “Bearer fc2ea31b”

  3. enable_mutual_tls (boolean) - whether to support mutual TLS during event delivery. See.

Creating a webhook subscription via the Percolate API

Let’s go ahead and create a webhook subscription. We will do this using the command line utility curl:

curl -X POST https://percolate.com/api/v5/webhook_subscription/ \
  -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’\
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Portal Webhook 1",
    "target_url": "https://en6p71m70qb6l.x.pipedream.net/",
    "message_ttl": 86400,
    "events": [
    {
        "object_type": "campaign",
        "change_type": "delete"
    },
    {
        "object_type": "campaign",
        "change_type": "create"
    },
    {
        "object_type": "post",
        "change_type": "update"
    },
    {
        "object_type": "post",
        "change_type": "create"
    }
    ],
    "scopes": [
    {
        "scope_id": "license:1"
    },
    {
        "scope_id": "license:2"
    }
    ],
    "headers": [
    {
        "key": "Authorization",
        "value": "Bearer xyz123"
    },
    {
        "key": "Cache-Control",
        "value": "no-cache"
    }
    ]
}'

Breakdown of this command:

  1. It’s a POST request to the webhook subscription API: curl -X POST https://percolate.com/api/v5/webhook_subscription/
  2. We specify the Authorization header containing an access token: -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’.

👍

This API is only accessible to users who have the System Admin role.

  1. We specify a Content-type header of application/json which is the format the payload is in: -H 'Content-Type: application/json'
  2. We specify the body, a JSON object with the attributes name, target_url, message_ttl, events, scopes and headers.

We get a response containing the webhook subscription when we make the request:

{
    "data": {
        "status": "enabled",
        "scopes": [
            {
                "scope_id": "license:1",
                "extend_scopes": false
            },
            {
                "scope_id": "license:2",
                "extend_scopes": false
            }
        ],
        "target_url": "https://en6p71m70qb6l.x.pipedream.net/",
        "updated_at": "2019-09-27T13:15:06+00:00",
        "id": "webhook_subscription:1156579774301030749",
        "name": "Portal Webhook 1",
        "created_at": "2019-09-27T13:15:06+00:00",
        "created_by": "user:1234",
        "headers": [
            {
                "value": "Bearer xyz123",
                "key": "Authorization"
            },
            {
                "value": "no-cache",
                "key": "Cache-Control"
            }
        ],
        "message_ttl": 86400,
        "enable_mutual_tls": false,
        "events": [
            {
                "object_type": "campaign",
                "change_type": "create"
            },
            {
                "object_type": "post",
                "change_type": "create"
            },
            {
                "object_type": "post",
                "change_type": "update"
            },
            {
                "object_type": "campaign",
                "change_type": "delete"
            }
        ]
    }
}

Creating a webhook subscription via the Percolate user interface

  1. To create a webhook subscription via the UI, we’ll go to Settings > Developer > Manage webhooks on the Percolate main menu.
  2. Once we click the New subscription button in the upper right corner, the Add a new webhook subscription modal will appear.
798798
  1. We’ll fill in the webhook subscription details name, target_url, message_ttl, events, scopes and headers.
722722
  1. On clicking the save button on the lower right corner, the subscription will be created. We can find it listed in Settings > Developer > Manage webhooks.

Next, we will be testing out whether this subscription is functional and can send events out to our application. Testing our webhook subscription involves doing actions on Percolate that generate events the webhook subscription is looking out for then checking if our target received them. For our subscription, we specified Campaign creation events. Therefore, if we create a Campaign, we should receive a “campaign created event” on our target URL.

Testing a webhook Subscription

For our subscription’s target URL, we used a URL from RequestBin; a service that allows us to create a webhook URL and shows us the requests made to it in a human-friendly way.

🚧

This is convenient for purposes of demonstration and testing and should not be used for webhook subscriptions that would send events containing production data.

To setup your own RequestBin endpoint, you can follow the instructions on their documentation: https://RequestBin.com/docs/#create-an-endpoint.

Upon creating a Campaign within one of the licenses the webhook subscription is on, Percolate makes an HTTP POST request to the target URL from RequestBin containing the event payload and headers. This should show up on the RequestBin UI:

16001600

This lets us know that the webhook subscription is working. We will be using Requestbin for some of our demos in this guide.

Testing a webhook subscription using the delivery logs API

We can also check whether a subscription is functional via the v5/webhook_log API. Whenever an attempt to deliver an event for a subscription is made, a log record is created that includes the request details and response Percolate got from the target server.

To access the delivery log records for our subscription, we’ll call the webhook logs API:

curl -X GET 'https://percolate.com/api/v5/webhook_log/?webhook_subscription_id=webhook_subscription:1156579774301030749' \
  -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd' \

Let’s break this command down:

  1. It’s a GET request to the webhook logs API: curl -X GET 'https://percolate.com/api/v5/webhook_log/
  2. We include our webhook subscription’s ID as a query parameter: ?webhook_subscription_id=webhook_subscription:1156579774301030749
  3. We specify the Authorization header containing an access token: -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’.

Once we make the request, we get a response containing a list of log records. Each log record looks like this:

{
           "webhook_subscription_id": "webhook_subscription:1156579774301030749",
           "response_status_code": 200,
           "response_headers": [
               {
                   "value": "Mon, 30 Sep 2019 13:07:47 GMT",
                   "key": "Date"
               },
               {
                   "value": "16",
                   "key": "Content-Length"
               },
               {
                   "value": "keep-alive",
                   "key": "Connection"
               },
               {
                   "value": "Express",
                   "key": "X-Powered-By"
               },
               {
                   "value": "sent to primary",
                   "key": "x-pd-status"
               },
               {
                   "value": "application/json; charset=utf-8",
                   "key": "Content-Type"
               },
               {
                   "value": "*",
                   "key": "Access-Control-Allow-Origin"
               }
           ],
           "request_body": "{\"change_type\": \"create\", \"object_id\": \"campaign:759120\", \"object_type\": \"campaign\", \"tenant_id\": \"tenant:1423\", \"event_time\": \"2019-09-30T13:07:46.904261+00:00\", \"actor_id\": \"user:120356\", \"object_value\": {\"topic_ids\": [], \"updated_at\": \"2019-09-30T13:07:46+00:00\", \"assignee_id\": \"user:120356\", \"id\": \"campaign:759120\", \"production_workflow_id\": null, \"post_workflow_ids\": [], \"title\": \"Portal Campaign 1\", \"object_step_id\": null, \"end_at\": null, \"parent_id\": null, \"actor_id\": null, \"production_workflow_ids\": [], \"metadata\": [], \"ordinal\": null, \"start_at\": null, \"description\": \"\", \"root_id\": null, \"platform_ids\": [], \"scope_id\": \"license:121507\", \"approval_ids\": [], \"created_at\": \"2019-09-30T13:07:46+00:00\", \"budget\": null, \"term_ids\": [], \"thumbnail_asset_id\": null}}",
           "target_url": "https://en6p71m70qb6l.x.pipedream.net/",
           "response_body": "{\"success\":true}",
           "created_at": "2019-09-30T13:07:47.318609+00:00",
           "id": "webhook_log:1157665096232108255",
           "request_headers": [
               {
                   "value": "Bearer xyz123",
                   "key": "Authorization"
               },
               {
                   "value": "777",
                   "key": "Content-Length"
               },
               {
                   "value": "gzip, deflate",
                   "key": "Accept-Encoding"
               },
               {
                   "value": "*/*",
                   "key": "Accept"
               },
               {
                   "value": "en6p71m70qb6l.x.pipedream.net",
                   "key": "Host"
               },
               {
                   "value": "no-cache",
                   "key": "Cache-Control"
               },
               {
                   "value": "application/json",
                   "key": "Content-Type"
               },
               {
                   "value": "Percolate Webhook/2.0",
                   "key": "User-Agent"
               }
           ]
       },

The following fields are included:

  1. created_at - UTC timestamp of when the log record was created.
  2. target_url - the target URL of the webhook subscription at the time of the delivery attempt.
  3. request_headers - array of all headers sent in the request. These include custom headers specified on the webhook subscription.
  4. request_body - the webhook event payload which is a JSON string.
  5. response_status_code - a HTTP response status code. This is 0 if the target URL was unreachable.
  6. response_headers - array of all headers the target URL responded with. This is null if the target was unreachable.
  7. response_body - string representation of the body of the response by the target. This is null if the target URL was unreachable.
  8. webhook_subscription_id - the ID of the webhook subscription that owns the log.

The logs are ordered from most recent to oldest, therefore, the latest logs are always at the top.

📘

Whenever Percolate is unable to deliver an event the first time, it retries delivery at intervals for the duration of the subscription’s message_ttl. Each retry attempt will create a separate log record.

Testing a webhook subscription using the delivery logs UI

The delivery logs are also presented on the Percolate UI. To view them:

  1. On the webhook subscriptions list, we’ll click on the webhook subscription we want to view logs for.
  2. On the webhook subscription view, we’ll click on the Delivery Logs tab that’s to the right of the Settings tab. The list view for the logs should show up.
  3. To view a specific log’s full details, we need to click on the Event Type name. In this view we will see the webhook event payload that was sent, the headers sent along with it, and the response status code, headers and body that the target endpoint sent back.
  4. To go back to the list view for all the logs, we can click on the All logs link on the top left.
  5. Whenever a new event occurs and a delivery attempt is made, a new log will show up on the delivery logs list for that subscription. To refresh the list, click on the Delivery Logs tab again.

In the case where you test your webhook subscription against your own application, ensure that responses have a status code between 200 and 299. Anything outside this range will be considered a failed delivery attempt and Percolate will retry delivery of the event.

Now that we know how to create and test a webhook subscription, let’s take a look at the anatomy of a webhook event payload.

Anatomy of a webhook event payload

The webhook event payload is a JSON representation of an event that happened on Percolate. An event happens whenever a Percolate object is created, edited or deleted. This JSON representation of the event is what your application will receive from Percolate. It has the following attributes:

object_type - the type of Percolate object that was affected by the change. e.g. Campaign, Content, Approval, etc.
change_type - the type of change that was applied to the object. This is one of create, update or delete.
object_id - the object id on which the change was applied. e.g campaign:142, post:31139
tenant_id - the tenant id on which the event happened.
event_time - the UTC timestamp of when the event happened.
actor_id - the id of the user who effected the change on the given object. This is null for change events that are caused by the system in the background.
object_value - JSON representation of the object before the change was applied. If it is a create change type, this is set to the new object’s value. The representation of this object is as would be received if the object were fetched via its REST API endpoint.
update - this field is only present if the change type for the event is update. It is a JSON patch diff showing what attributes have been added or changed and the new values they should have. JSON Patch is a format for describing changes to a JSON document. This diff allows your application to compare the old and new state of the object.

You can find more about the webhook event schema here: https://percolate.dev/docs/webhook-event-schema.

The following is a sample event with a diff describing a post whose title was changed:

{
        "change_type": "update",
        "object_id": "post:2",
        "object_type": "post",
        "tenant_id": "tenant:1",
        "event_time": "2019-10-03T15:35:36.567289+00:00",
        "actor_id": "user:2",
        "object_value": {
            "ext": {
                "mentions": [],
                "message": "",
                "is_promoted": false,
                "branded_content": null,
                "message_links": []
            },
            "ingested": false,
            "term_ids": [],
            "actor_id": "user:2",
            "live_at": null,
            "scope_id": "license:9",
            "platform_id": "platform:1158411283150134144",
            "is_valid": false,
            "interaction_id": null,
            "topic_ids": [],
            "asset_ids": [],
            "targeting_id": null,
            "created_at": "2019-10-02T19:43:58+00:00",
            "assignee_id": "user:2",
            "name": "Post title",
            "object_step_id": null,
            "url": null,
            "user_id": "user:2",
            "metadata": [],
            "live_at_timezone": null,
            "status": "draft",
            "link_ids": [],
            "xid": "",
            "production_workflow_id": null,
            "channel_id": "channel:1158411963018498833",
            "updated_at": "2019-10-03T15:18:13+00:00",
            "schema_id": "schema:1158411286373588508_1158411286373588508",
            "status_message": null,
            "id": "post:2",
            "post_attachment_ids": [],
            "approval_ids": [],
            "description": "",
            "approval_workflow_id": null,
            "origin_ids": []
        },
        "update": [
            {
                "path": "/updated_at",
                "value": "2019-10-03T15:35:36+00:00",
                "op": "replace"
            },
            {
                "path": "/name",
                "value": "New post title",
                "op": "replace"
            }
        ]
}

You can find out more about the JSON Patch specification, existing library implementations of it for languages such as Javascript, Python, PHP, Ruby, Java, and more here: http://jsonpatch.com/.

Updating a webhook subscription’s details

Via the Percolate API

The v5/webhook_subscription API allows you to update any of the details on a webhook subscription. This includes the events, headers, message_ttl, enable_mutual_tls, names, scopes, status and target_url fields.

Let’s go ahead and update the message_ttl on the webhook subscription we created earlier:

curl -X PUT https://percolate.com/api/v5/webhook_subscription/webhook_subscription:1156579774301030749 \
  -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’ \
  -H 'Content-Type: application/json' \
  -d '{
    "message_ttl": 3600
}'

In this command we:

  1. Make a PUT request to the v5/webhook_subscription API, specifying the ID of the webhook subscription whose details we want to change in the path parameters: PUT https://percolate.com/api/v5/webhook_subscription/webhook_subscription:1156579774301030749
  2. Pass in the authorization and content type headers: -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’ -H 'Content-Type: application/json'
  3. Pass in the JSON request body containing the webhook subscription message_ttl.

When we run this, we should get a response containing the updated webhook subscription with its message_ttl set to 3600.

{
    "data": {
        "status": "disabled",
        "scopes": [
            {
                "scope_id": "license:1",
                "extend_scopes": false
            },
            {
                "scope_id": "license:2",
                "extend_scopes": false
            }
        ],
        "target_url": "https://en6p71m70qb6l.x.pipedream.net/",
        "updated_at": "2019-10-04T14:16:47+00:00",
        "id": "webhook_subscription:1156579774301030749",
        "name": "Portal Webhook 1",
        "created_at": "2019-09-27T13:15:06+00:00",
        "created_by": "user:1234",
        "headers": [
            {
                "value": "Bearer xyz123",
                "key": "Authorization"
            },
            {
                "value": "no-cache",
                "key": "Cache-Control"
            }
        ],
        "message_ttl": 3600,
        "enable_mutual_tls": false,
        "events": [
            {
                "object_type": "post",
                "change_type": "create"
            },
            {
                "object_type": "campaign",
                "change_type": "create"
            },
            {
                "object_type": "campaign",
                "change_type": "delete"
            },
            {
                "object_type": "post",
                "change_type": "update"
            }
        ]
    }
}

Now in case the first delivery attempt fails, Percolate will keep retrying delivery of the event for only 1 hour before discarding it.

Updating array type fields

The events, scopes and headers fields on a webhook subscription are arrays. Percolate’s API doesn’t support partial updates to arrays therefore the array passed in the request entirely replaces the one that’s already there. This means that when adding new values, we need to pass in the old ones too so as not to lose them.

Let’s go ahead and update the headers on our webhook subscription from earlier to include an additional custom header X-My-Header:

curl -X PUT https://percolate.com/api/v5/webhook_subscription/webhook_subscription:1156579774301030749 \
  -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’ \
  -H 'Content-Type: application/json' \
  -d '{
    "headers": [
            {
                "value": "Bearer xyz123",
                "key": "Authorization"
            },
            {
                "value": "no-cache",
                "key": "Cache-Control"
            },
            {
             "key": "X-My-Header",
             "value": "yes"
            }
    ]
}'

Upon running this, we get a response containing the updated webhook subscription with the new header value:

{
    "data": {
        ...
        "headers": [
            {
                "value": "Bearer xyz123",
                "key": "Authorization"
            },
            {
                "value": "no-cache",
                "key": "Cache-Control"
            },
            {
                "value": "yes",
                "key": "X-My-Header"
            }
        ]
      ...
    }
}

Whenever Percolate is delivering an event for our subscription, it will include this new header now.

Via the Percolate UI

To update a webhook subscription via the UI:

  1. We’ll navigate to the webhook subscription details view by clicking on the subscription’s name in the list view.
  2. When we edit any part of the webhook subscription, a dialog will appear asking us whether you want to save the changes. If we click Save, the subscription will be updated. Here we edit the subscription target URL and events.

Next we’ll be taking a look at updating a special webhook subscription field status and the meaning of this field.

Deactivating a webhook subscription

Via the Percolate API

When a webhook subscription is created, it is active by default. This means that Percolate will send out events that happen to that subscription. If a webhook subscription is deactivated, Percolate will stop sending events to it.

The status field on a webhook subscription is used to indicate whether a subscription is active or inactive. It is set to “enabled” when active and “disabled” when inactive.

Let’s deactivate our webhook subscription:

curl -X PUT https://percolate.com/api/v5/webhook_subscription/webhook_subscription:1156579774301030749 \
  -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’ \
  -H 'Content-Type: application/json' \
  -d '{
    "status": "disabled"
}'

Making this request will give us back a response containing the updated webhook subscription that has the status field set to disabled. This means that we will no longer get notified when Campaigns are created and deleted nor when pieces of Content are created or updated.

To re-enable a subscription, we’d make the same request but this time set the statu to enabled.

curl -X PUT https://percolate.com/api/v5/webhook_subscription/webhook_subscription:1156579774301030749 \
  -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’ \
  -H 'Content-Type: application/json' \
  -d '{
    "status": "enabled"
}'

Making this request will give us back a response containing the updated webhook subscription that has the status field set to enabled. We will now start receiving any events that happen.

🚧

A re-enabled subscription will not receive any events that happened while it was deactivated.

Next, we’ll take a look at permanently deleting a webhook subscription.

Via the Percolate UI

On the list of webhook subscriptions, once we find the subscription we want to delete, we can click the vertical ellipsis that’s on the right and then click the Deactivate button on the menu that pops up. This will open up a confirmation dialog asking us to confirm the deactivation and the subscription will be deactivated upon confirmation.

Deleting a webhook subscription

Via the Percolate API

Webhook subscriptions that we no longer need can be deleted. This action is irreversible. Percolate will stop sending events once a subscription is deleted. Since we’re done with this demo, let’s go ahead and delete the webhook subscription we created for it:

curl -X DELETE https://percolate.com/api/v5/webhook_subscription/webhook_subscription:1156579774301030749 \
  -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’

Breakdown of this command:

  1. We make a DELETE request to the v5/webhook_subscription API, specifying the ID of the webhook subscription whose details we want to change in the path parameters: curl -X DELETE https://percolate.com/api/v5/webhook_subscription/webhook_subscription:1156579774301030749
  2. Pass in the authorization header: -H 'Authorization: Bearer X0g4fYBlepAY6TYtoH5U1zAlvun5b3Pd’

Upon running this, we should get a response with a status code of 204 that tells us the delete was successful.

If we try to fetch that subscription now, we’ll get a 403 response with the following body:

{
    "errors": [
        {
            "message": null,
            "code": "authorization:forbidden"
        }
    ]
}

Now that the subscription is deleted, we will no longer get notified whenever the events we originally specified on the subscription happen.

Via the Percolate UI

On the list of webhook subscriptions, once we find the subscription we want to delete, we should click the vertical ellipsis that’s on the right and then click the Delete button on the menu that pops up. This will open up a confirmation dialog asking us to confirm the deletion and the subscription will be deleted upon confirmation. Once the subscription is deleted, it will no longer show up on the webhook subscriptions list.

Troubleshooting & FAQ

I’m not receiving events on a subscription.
You may fail to receive events on a subscription due to several factors. You want to ensure that:

-The subscription’s target_url is correct and has no typos in it. If it’s incorrect, you may find failures appearing in the logs.
-Your application is running and exposing the appropriate endpoint. If it isn’t you would find logs with a response_status_code of 0, 4xx or 5xx.
-The webhook subscription is activated.
-The events you’re interested in receiving are specified on the webhook subscription
-The webhook subscription has been enabled for the appropriate teams/scopes.

What response should my application give after receiving an event?
Percolate will consider only consider a delivery successful if the response has a status code between 200 and 299. Any other status code is considered a failure and Percolate will retry delivery.

Why have some logs on a subscription disappeared?
Percolate deletes delivery logs on a webhook subscription that are more than 2 weeks old.

Why is the webhook event actor_id attribute null?
When you receive a webhook event that has a null actor_id, it means that that event was generated as a result of an automated process within Percolate (such as Content being published at a scheduled time) as opposed to being generated from user actions.

Why is there no update attribute on the webhook event?
The update attribute is only added to webhook events that have a change_type of update. Create and delete events will not contain this attribute.