Using webhooks

So far in this series we've added user data, custom and standard attributes, leads and companies, and then created events and segments to better understand our users' behavior and actions.

By doing this you may have identified that there are certain user actions you rate as particularly important and want to be notified of when they occur. One way you can do this is via webhooks, which allow you to receive notifications when users take specific actions.

You can read more about webhooks in our docs where we describe some common use cases and go over how you can interact with webhooks via the web app console.

Setting up your endpoint

Webhooks work by POSTing data to an endpoint which you specify when you setup the endpoint. So before we create a webhook we will need to create an endpoint which is capable of receiving a POST request. The easiest way to do this for testing purposes is via ngrok with a simple Sinatra route.

Ngrok setup

To setup ngrok simply download the free package from their website for your OS. Ngrok allows you to do you development on localhost but then make it available publicly via secure tunnels. You can find the simple setup steps on the ngrok website.

Once you have these setup you should be able to run the following command to make the tunnel available:

> ./ngrok http  4567
Tunnel Status                 online
Version                       2.0.25/2.0.25
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://71b0744d.ngrok.io -> localhost:4567
Forwarding                    https://71b0744d.ngrok.io -> localhost:4567

Sinatra setup

For now all we need is a simple endpoint which listens for a POST request. Sinatra is a lightweight framework which we can use to setup a quick and simple POST endpoint. You should be able to test this route with a simple curl POST as shown below. Ensure that you are using your ngrok tunnel as outlined in the output you see when running ngrok.

require 'sinatra'
require 'json'

#You may not need to bind the server to 0.0.0.0
#It automatically binds to 127.0.0.0 
set :bind, '0.0.0.0'

post '/webhook' do
  push = JSON.parse(request.body.read)
  puts "Webhook JSON Data: #{push.inspect}"
end
> ruby webhooks_server.rb
> curl --data @test.json http://69b97777.ngrok.io/webhook

Webhook JSON Data: {"title"=>"TEST", "body"=>"This is a test post."}
89.101.228.226 - - [09/May/2016:15:07:40 +0000] "POST /webhook HTTP/1.1" 200 - 0.0051
{
  "title": "TEST",
  "body": "This is a test post."
}

Creating a webhook

Now that we have out server setup, we can create a webhook and see if we receive a notification when the relevant action occurs. When we create a webhook we subscribe to topics, which are the actions or events we want to be notified of when they occur.

Here is the list of topics which you can subscribe to (you can subscribe to multiple topics per webhook). You can also find the list on our API reference guide here. For the purposes of this tutorial we will look at user and event topics.

Topic
Item Type
Description

conversation

Conversation

Subscribe to all conversation notifications

conversation.user.created

Conversation

Subscribe to user initiated messages

conversation.user.replied

Conversation

Subscribe to user conversation replies

conversation.admin.replied

Conversation

Subscribe to admin conversation replies

conversation.admin.assigned

Conversation

Subscribe to admin conversation assignments

conversation.admin.closed

Conversation

Subscribe to admin conversation closes

conversation.admin.noted

Conversation

Subscribe to admin conversation notes

conversation.admin.opened

Conversation

Subscribe to admin conversation opens

user.created

User

Subscribe to user creations

user.deleted

User

Subscribe to user deletions. Not sent for bulk deletions.

user.unsubscribed

User

Subscribe to user unsubscriptions from email

user.email.updated

User

Subscribe to user's email address being updated

user.tag.created

User

Subscribe to users being tagged. Not sent for bulk deletions.

user.tag.deleted

User

Subscribe to users being untagged

contact.created

Lead

Subscribe to Lead creations

contact.signed_up

Lead

Subscribe to Leads converting to a User

contact.added_email

Lead

Subscribe to Leads adding email

company.created

Company

Subscribe to company creations

event.created

Event

Subscribe to events (Beta!)

ping

Ping

Sent when a post to the Subscription's ping resource is received, or periodically by Intercom. Ping is always subscribed to.

Create a file called webhook_tasks.rb and add the following code to it:

require './intercom_client'

class WebhookTasks < IntercomClient
  def initialize
    #Leave this empty as simple over-ride
  end

  def create_webhook(url, topics)
    #Create a hash to pass through the Webhook data
    webhook = {:url => url,
               :topics => topics}
    @@intercom.subscriptions.create(webhook)
  end
end
> require './webhook_tasks'
=> true
> intercom = IntercomClient.new('<APP ID>','<API KEY>')
=> #<IntercomClient:0x00000001e240e0>
[6] pry(main)> web = WebhookTasks.new
=> #<WebhookTasks:0x00000001d75450>
[7] pry(main)> web.create_webhook('http://14856885.ngrok.io/webhook', ["users.created"])
=> #<Intercom::Subscription:0x00000001cc5f50

You should also see that you receive an immediate "ping" from the webhook you just setup to test if the URL is available for POST requests. You should see the Sinatra output indicating the a POST was received with some JSON data and an ngrok update to note that a POST was received successfully (via 200 HTTP status code). You can also check via the console to ensure that you can see that webhook successfully created:

Webhook JSON Data: {"type"=>"notification_event", "app_id"=>"<APP ID>", "data"=>{"type"=>"notification_event_data", "item"=>{"type"=>"ping", "message"=>"something something interzen"}}, "links"=>{}, "id"=>nil, "topic"=>"ping", "delivery_status"=>nil, "delivery_attempts"=>1, "delivered_at"=>0, "first_sent_at"=>1462880405, "created_at"=>1462880405, "self"=>nil}
54.87.242.237 - - [10/May/2016:11:40:05 +0000] "POST /webhook HTTP/1.1" 200 - 0.0009
Tunnel Status                 online
Version                       2.0.25/2.1.1
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://14856885.ngrok.io -> localhost:4567
Forwarding                    https://14856885.ngrok.io -> localhost:4567

Connections                   ttl     opn     rt1     rt5     p50     p90
                              5       0       0.00    0.00    0.28    39.97

HTTP Requests
-------------

POST /webhook                  200 OK

You can check the developer hub dashboard to see that the webhook is setup correctly. You will see a list of your current webhooks:

Listing your webhooks

Now that you have created a webhook you'll want to be able to list your webhooks to see what is currently setup.

To make it easy to list your subscriptions, let's create a method which allows you to specify the attribute of the subscription model that you want to see. The method should then iterate over the subscriptions model (which can be seen here) and output that attribute from each subscription. Alternatively, if you do not specify any attribute, let's output a short list of the most important attributes such as subscriptions ID, callback URL, webhook status and the topics subscribed to:

  def list_subscriptions(attrib=false)
    #Check if there is an attribute specified
    if attrib
      #If there is then assume just this attribute is needed
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.send(attrib.to_sym)}"};
    else
      #Print out default of most useful attributes
    @@intercom.subscriptions.all.each {|sub| puts "#{sub.id}, #{sub.url}, #{sub.active}, #{sub.topics}"};
    end
  end
require './intercom_client'

class WebhookTasks < IntercomClient
  def initialize
    #Leave this empty as simple over-ride
  end

  def create_webhook(url, topics)
    #Create a hash to pass through the Webhook data
    webhook = {:url => url,
               :topics => topics}
    @@intercom.subscriptions.create(webhook)
  end

  def list_subscriptions(attrib=false)
    #Check if there is an attribute specified
    if attrib
      #If there is then assume just this attribute is needed
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.send(attrib.to_sym)}"};
    else
      #Print out default of most useful attributes
    @@intercom.subscriptions.all.each {|sub| puts "#{sub.id}, #{sub.url}, #{sub.active}, #{sub.topics}"};
    end
  end
end

You can then list individual or default attributes as follows:

> require './webhook_tasks';
> intercom = IntercomClient.new('<APP ID>','<API KEY>');
> web = WebhookTasks.new;
> web.list_subscriptions();
nsub_01033700-16ad-11e6-aa08-ed65c3040c0f, http://14856885.ngrok.io/webhook, true, ["user.created"]
> web.list_subscriptions("service_type");
web

If you want to access a single subscription object we can create a simple method to get this:

  def get_subscription(id)
    #The ID is the only unique element of the webhook
    @@intercom.subscriptions.find(:id => id)
  end
require './intercom_client'

class WebhookTasks < IntercomClient
  def initialize
    #Leave this empty as simple over-ride
  end

  def create_webhook(url, topics)
    #Create a hash to pass through the Webhook data
    webhook = {:url => url,
               :topics => topics}
    @@intercom.subscriptions.create(webhook)
  end

  def list_subscriptions(attrib=false)
    #Check if there is an attribute specified
    if attrib
      #If there is then assume just this attribute is needed
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.send(attrib.to_sym)}"};
    else
      #Print out default of most useful attributes
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.id}, #{sub.url}, #{sub.active}, #{sub.topics}"};
    end
  end

  def get_subscription(id)
    #The ID is the only unique element of the webhook
    @@intercom.subscriptions.find(:id => id)
  end
end

We can use the list subscriptions method we just created to see the IDs and then get them individually:

> web.list_subscriptions();
nsub_01033700-16ad-11e6-aa08-ed65c3040c0f, http://14856885.ngrok.io/webhook, true, ["user.created"]
> sub = web.get_subscription("nsub_01033700-16ad-11e6-aa08-ed65c3040c0f");
> sub.topics
=> ["user.created"]

Test your webhook

Now that we have created a new webhook, we should test it. We subscribed to be notified whenever a new user is created, so let's create a new user and see if we get notified when this occurs.

Remember to watch the terminal to see that ngrok identifies a 200 HTTP Status code and that you see some output from your Sinatra server indicating you have received some JSON data about the action. Create a new user using some of the code we created in our earlier tutorials:

> require './user_tasks';
> intercom = IntercomClient.new('<APP ID>','<API KEY>');
> usr = UserTasks.new;
> usr.create_user(:email => "husserl@phenom.com", :user_id => 20)

You should see a notification to your Sinatra server which looks something like this:

52.90.74.244 - - [10/May/2016:12:44:58 +0000] "POST /webhook HTTP/1.1" 200 - 0.0008
Webhook JSON Data: {"type"=>"notification_event", "app_id"=>"<APP ID>", "data"=>{"type"=>"notification_event_data", "item"=>{"type"=>"user", "id"=>"5731e7a3ec06c24bee0000f5", "user_id"=>"20", "anonymous"=>false, "email"=>"husserl@phenom.com", "name"=>nil, "pseudonym"=>nil, "avatar"=>{"type"=>"avatar", "image_url"=>nil}, "app_id"=>"a86dr8yl", "companies"=>{"type"=>"company.list", "companies"=>[]}, "location_data"=>{}, "last_request_at"=>nil, "last_seen_ip"=>nil, "created_at"=>"2016-05-10T13:52:35.548Z", "remote_created_at"=>nil, "signed_up_at"=>nil, "updated_at"=>"2016-05-10T13:52:35.548Z", "session_count"=>0, "social_profiles"=>{"type"=>"social_profile.list", "social_profiles"=>[]}, "unsubscribed_from_emails"=>false, "user_agent_data"=>nil, "tags"=>{"type"=>"tag.list", "tags"=>[]}, "segments"=>{"type"=>"segment.list", "segments"=>[]}, "custom_attributes"=>{}}}, "links"=>{}, "id"=>"notif_73538090-16b6-11e6-aa08-ed65c3040c0f", "topic"=>"user.created", "delivery_status"=>nil, "delivery_attempts"=>1, "delivered_at"=>0, "first_sent_at"=>1462888355, "created_at"=>1462888355, "self"=>nil}
Tunnel Status                 online
Version                       2.0.25/2.1.1
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://14856885.ngrok.io -> localhost:4567
Forwarding                    https://14856885.ngrok.io -> localhost:4567

Connections                   ttl     opn     rt1     rt5     p50     p90
                              8       0       0.00    0.00    0.16    39.97

HTTP Requests
-------------

POST /webhook                  200 OK
POST /webhook                  200 OK

Create another subscription

Let's create another subscription to notify us when users are deleted. This way we can delete the user we just created and receive a notification for this.

> require './webhook_tasks';
> intercom = IntercomClient.new('a86dr8yl','a42be65ea1cba4f4d6d6dfbe8ed5e2aa1f1ae32a');
> web = WebhookTasks.new;
> web.create_webhook('http://14856885.ngrok.io/webhook', ["user.deleted"])
> web.list_subscriptions
nsub_8208ed10-16ba-11e6-bbf4-310abb4a0524, http://14856885.ngrok.io/webhook, true, ["user.deleted"]
nsub_01033700-16ad-11e6-aa08-ed65c3040c0f, http://14856885.ngrok.io/webhook, true, ["user.created"]

You should see a Ping to note that the webhook was successfully created:

Webhook JSON Data: {"type"=>"notification_event", "app_id"=>"<APP ID>", "data"=>{"type"=>"notification_event_data", "item"=>{"type"=>"ping", "message"=>"something something interzen"}}, "links"=>{}, "id"=>nil, "topic"=>"ping", "delivery_status"=>nil, "delivery_attempts"=>1, "delivered_at"=>0, "first_sent_at"=>1462890098, "created_at"=>1462890098, "self"=>nil}
54.87.242.237 - - [10/May/2016:14:21:38 +0000] "POST /webhook HTTP/1.1" 200 - 0.0007

Now we should be able to delete our new user and receive a webhook notification for this:

> require './user_tasks';
> usr = UserTasks.new;
> usr.delete_user("husserl@phenom.com");
Webhook JSON Data: {"type"=>"notification_event", "app_id"=>"<APP ID>", "data"=>{"type"=>"notification_event_data", "item"=>{"type"=>"user", "id"=>"5731e7a3ec06c24bee0000f5", "user_id"=>"20", "email"=>"husserl@phenom.com"}}, "links"=>{}, "id"=>"notif_300fb510-16bb-11e6-bbf4-310abb4a0524", "topic"=>"user.deleted", "delivery_status"=>nil, "delivery_attempts"=>1, "delivered_at"=>0, "first_sent_at"=>1462890390, "created_at"=>1462890390, "self"=>nil}
54.87.242.237 - - [10/May/2016:14:26:30 +0000] "POST /webhook HTTP/1.1" 200 - 0.0006

Deleting a subscription

If you want to delete a subscription, you can do so via the web app console but for now we will delete our subscriptions using the API. We can create a simple wrapper for the API to delete our subscriptions:

  def delete_subscriptions(id)
    #Delete single subscription given Subscription ID
    #We dont have any query parameters so 2nd function param is empty string
    @@intercom.delete("/subscriptions/#{id}", "")
  end
require './intercom_client'

class WebhookTasks < IntercomClient
  def initialize
    #Leave this empty as simple over-ride
  end

  def create_webhook(url, topics)
    #Create a hash to pass through the Webhook data
    webhook = {:url => url,
               :topics => topics}
    @@intercom.subscriptions.create(webhook)
  end

  def list_subscriptions(attrib=false)
    #Check if there is an attribute specified
    if attrib
      #If there is then assume just this attribute is needed
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.send(attrib.to_sym)}"};
    else
      #Print out default of most useful attributes
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.id}, #{sub.url}, #{sub.active}, #{sub.topics}"};
    end
  end

  def get_subscription(id)
    #The ID is the only unique element of the webhook
    @@intercom.subscriptions.find(:id => id)
  end

  def delete_subscriptions(id)
    #Delete single subscription given Subscription ID
    #We dont have any query parameters so 2nd function param is empty string
    @@intercom.delete("/subscriptions/#{id}", "")
  end
end

Then you should be able to simply delete a subscription via the following steps:

> require './webhook_tasks';
> intercom = IntercomClient.new('<APP ID>','<API KEY>');
> web = WebhookTasks.new;
> web.list_subscriptions;
nsub_077a7a00-16ce-11e6-bbf4-310abb4a0524, http://14856885.ngrok.io/webhook, true, ["user.created"]
nsub_113181b0-16ce-11e6-bbf4-310abb4a0524, http://14856885.ngrok.io/webhook, true, ["user.deleted"]
> web.delete_subscriptions("nsub_113181b0-16ce-11e6-bbf4-310abb4a0524")
> web.list_subscriptions;
nsub_077a7a00-16ce-11e6-bbf4-310abb4a0524, http://14856885.ngrok.io/webhook, true, ["user.created"]
> web.delete_subscriptions("nsub_077a7a00-16ce-11e6-bbf4-310abb4a0524")
> web.list_subscriptions;
>

Event webhooks

As well as default topics, you can also subscribe for events which you have created. This allows you to track particular user behavior you may be interested in. There is an "event.created" topic that you can subscribe to in order to identify that there are custom events you want to be notified about. Then you need to pass in the event(s) you want to be notified about.

The easiest thing to do here would be to add the functionality to the method we already have for creating webhooks. We will need to provide some extra information when creating custom events so we can make this an optional parameter. This way we know if it is a standard topic or an event we are subscribing to.

  def create_webhook(url, topics, events=false)
    if events
      event_data = {
          service_type: "web",
          topics: ["event.created"],
          url: url,
          metadata: {
          event_names: events
          }
      }
      #Need to POST event metadata for event.created topic
      @@intercom.post("/subscriptions/", event_data)
    else
      #Create a hash to pass through the Webhook data
      webhook = {:url => url,
                 :topics => topics}
      @@intercom.subscriptions.create(webhook)
    end
  end
require './intercom_client'

class WebhookTasks < IntercomClient
  def initialize
    #Leave this empty as simple over-ride
  end

  def create_webhook(url, topics, events=false)
    if events
      event_data = {
          service_type: "web",
          topics: ["event.created"],
          url: url,
          metadata: {
          event_names: events
          }
      }
      #Need to POST event metadata for event.created topic
      @@intercom.post("/subscriptions/", event_data)
    else
      #Create a hash to pass through the Webhook data
      webhook = {:url => url,
                 :topics => topics}
      @@intercom.subscriptions.create(webhook)
    end
  end

  def list_subscriptions(attrib=false)
    #Check if there is an attribute specified
    if attrib
      #If there is then assume just this attribute is needed
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.send(attrib.to_sym)}"};
    else
      #Print out default of most useful attributes
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.id}, #{sub.url}, #{sub.active}, #{sub.topics}"};
    end
  end

  def get_subscription(id)
    #The ID is the only unique element of the webhook
    @@intercom.subscriptions.find(:id => id)
  end

  def delete_subscriptions(id)
    #Delete single subscription given Subscription ID
    #We dont have any query parameters so 2nd function param is empty string
    @@intercom.delete("/subscriptions/#{id}", "")
  end
end

Before we subscribe to an event let's create some events using the code from our previous platform tutorial. If you already have some events created from these tutorials or from some other source you can skip this step and just use those events.

> require './event_tasks'
> event = EventTasks.new()
> intercom = IntercomClient.new('<APP ID>','API KEY');
> event.submit_event("wrote-fear-and-trembling", Time.now.to_i, "kierkegaard@existentialist.com")
> event.submit_event("wrote-either-or", Time.now.to_i, "kierkegaard@existentialist.com")

You should then be able to see your events in the Apps-Settings->Events section of the menu:

One point to note here is that the "EVENT NAME" may not match the event you created. For the UI the event name is slightly changed. For example, we created an event with the name "wrote-fear-and-trembling", but the "EVENT NAME" in the UI is "Wrote fear and trembling". This is important if you are referring to the events in actions such as webhooks. You will need to refer to the original event name and not the UI event name. You can always check the event names in the user's profile page:

Subscribe to events

Now that we have some events we can use our new code to create a webhook for them:

> require './webhook_tasks';
> intercom = IntercomClient.new('APP ID','API KEY');
> web = WebhookTasks.new;
> web.list_subscriptions;
>web.create_webhook('http://123.ngrok.io', ["event.created"], events=["wrote-fear-and-trembling", "invited-philosopher"])
> web.list_subscriptions;
> nsub_97fa6750-184c-11e6-b3f3-4b0d158bae78, http://123.ngrok.io, true, ["event.created"]
> subs = web.get_subscription('nsub_97fa6750-184c-11e6-b3f3-4b0d158bae78');
)> subs.topics
=> ["event.created"]
> subs.metadata
=> {"event_names"=>["wrote-fear-and-trembling", "invited-philosopher"]}
> web.create_webhook('http://1234.ngrok.io', ["user.created"])
web.list_subscriptions;
nsub_61a1d3d0-184e-11e6-b3f3-4b0d158bae78, http://1234.ngrok.io, true, ["user.created"]
nsub_97fa6750-184c-11e6-b3f3-4b0d158bae78, http://123.ngrok.io, true, ["event.created"]
> subs.topics
=> ["user.created"]
> subs.metadata
=> {}

Updating webhooks

While you are working with webhooks you may find that there are times when you need to update a particular webhook, since the callback has changed or you need to subscribe to more topics. You can do this with a POST request to the ID of the subscription of the webhook you want to update.

You add the data you want to update to the body of the POST. We can create a new method to do this:

  def update_webhook(id, url, new_topics)
    #Create the data you are going to POST
    updates = {
        topics: new_topics,
        url: url,
        }
    @@intercom.post("/subscriptions/#{id}", updates)
  end
require './intercom_client'

class WebhookTasks < IntercomClient
  def initialize
    #Leave this empty as simple over-ride
  end

  def create_webhook(url, topics, events=false)
    if events
      event_data = {
          service_type: "web",
          topics: ["event.created"],
          url: url,
          metadata: {
          event_names: events
          }
      }
      #Need to POST event metadata for event.created topic
      @@intercom.post("/subscriptions/", event_data)
    else
      #Create a hash to pass through the Webhook data
      webhook = {:url => url,
                 :topics => topics}
      @@intercom.subscriptions.create(webhook)
    end
  end

  def update_webhook(id, url, new_topics)
    #Create the data you are going to POST
    updates = {
        topics: new_topics,
        url: url,
        }
    @@intercom.post("/subscriptions/#{id}", updates)
  end

  def list_subscriptions(attrib=false)
    #Check if there is an attribute specified
    if attrib
      #If there is then assume just this attribute is needed
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.send(attrib.to_sym)}"};
    else
      #Print out default of most useful attributes
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.id}, #{sub.url}, #{sub.active}, #{sub.topics}"};
    end
  end

  def get_subscription(id)
    #The ID is the only unique element of the webhook
    @@intercom.subscriptions.find(:id => id)
  end

  def delete_subscriptions(id)
    #Delete single subscription given Subscription ID
    #We dont have any query parameters so 2nd function param is empty string
    @@intercom.delete("/subscriptions/#{id}", "")
  end
end

Then we can update the webhooks we just created as follows to add the "user.delete" topic:

web.update_webhook('nsub_61a1d3d0-184e-11e6-b3f3-4b0d158bae78', 'http://1234.ngrok.io', ['user.created', 'user.deleted'])
web.list_subscriptions;
nsub_61a1d3d0-184e-11e6-b3f3-4b0d158bae78, http://1234.ngrok.io, true, ["user.created", "user.deleted"]
nsub_97fa6750-184c-11e6-b3f3-4b0d158bae78, http://123.ngrok.io, true, ["event.created"]

Updating event webhooks

There is an extra metadata parameter for event webhooks so we will need to change the code a little to allow for this. Add the following code to your update method and then you should be able to update your event webhooks:

  def update_webhook(id, url, new_topics, events=false)
    #Create the data you are going to POST
    updates = {
        topics: new_topics,
        url: url,
        }
    if events
      updates[:metadata] = {event_names: events}
    end
    @@intercom.post("/subscriptions/#{id}", updates)
  end
require './intercom_client'

class WebhookTasks < IntercomClient
  def initialize
    #Leave this empty as simple over-ride
  end

  def create_webhook(url, topics, events=false)
    if events
      event_data = {
          service_type: "web",
          topics: ["event.created"],
          url: url,
          metadata: {
          event_names: events
          }
      }
      #Need to POST event metadata for event.created topic
      @@intercom.post("/subscriptions/", event_data)
    else
      #Create a hash to pass through the Webhook data
      webhook = {:url => url,
                 :topics => topics}
      @@intercom.subscriptions.create(webhook)
    end
  end

  def update_webhook(id, url, new_topics, events=false)
    #Create the data you are going to POST
    updates = {
        topics: new_topics,
        url: url,
        }
    if events
      updates[:metadata] = {event_names: events}
    end
    @@intercom.post("/subscriptions/#{id}", updates)
  end

  def list_subscriptions(attrib=false)
    #Check if there is an attribute specified
    if attrib
      #If there is then assume just this attribute is needed
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.send(attrib.to_sym)}"};
    else
      #Print out default of most useful attributes
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.id}, #{sub.url}, #{sub.active}, #{sub.topics}"};
    end
  end

  def get_subscription(id)
    #The ID is the only unique element of the webhook
    @@intercom.subscriptions.find(:id => id)
  end

  def delete_subscriptions(id)
    #Delete single subscription given Subscription ID
    #We dont have any query parameters so 2nd function param is empty string
    @@intercom.delete("/subscriptions/#{id}", "")
  end
end

You can then update your events by simply adding an events parameter to the update method:

> sub = web.get_subscription('nsub_97fa6750-184c-11e6-b3f3-4b0d158bae78')
> sub.metadata
=> {"event_names"=>["wrote-fear-and-trembling", "invited-philosopher"]}
> web.update_webhook('nsub_97fa6750-184c-11e6-b3f3-4b0d158bae78', 'http://123.ngrok.io', ['event.created'], events=["invited-philosopher"])
> sub = = web.get_subscription('nsub_97fa6750-184c-11e6-b3f3-4b0d158bae78')
> sub.metadata
=> {"event_names"=>["invited-philosopher"]}

Ping test

There is also an option to allow you to send a ping request to the webhook to test that it is working. You can do this with a simple post as shown below:

  def ping(id)
    #ping subscription to test webhook
    @@intercom.post("/subscriptions/#{id}/ping", "")
  end
require './intercom_client'

class WebhookTasks < IntercomClient
  def initialize
    #Leave this empty as simple over-ride
  end

  def create_webhook(url, topics, events=false)
    if events
      event_data = {
          service_type: "web",
          topics: ["event.created"],
          url: url,
          metadata: {
          event_names: events
          }
      }
      #Need to POST event metadata for event.created topic
      @@intercom.post("/subscriptions/", event_data)
    else
      #Create a hash to pass through the Webhook data
      webhook = {:url => url,
                 :topics => topics}
      @@intercom.subscriptions.create(webhook)
    end
  end

  def update_webhook(id, url, new_topics, events=false)
    #Create the data you are going to POST
    updates = {
        topics: new_topics,
        url: url,
        }
    if events
      updates[:metadata] = {event_names: events}
    end
    @@intercom.post("/subscriptions/#{id}", updates)
  end

  def list_subscriptions(attrib=false)
    #Check if there is an attribute specified
    if attrib
      #If there is then assume just this attribute is needed
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.send(attrib.to_sym)}"};
    else
      #Print out default of most useful attributes
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.id}, #{sub.url}, #{sub.active}, #{sub.topics}"};
    end
  end

  def get_subscription(id)
    #The ID is the only unique element of the webhook
    @@intercom.subscriptions.find(:id => id)
  end

  def delete_subscriptions(id)
    #Delete single subscription given Subscription ID
    #We dont have any query parameters so 2nd function param is empty string
    @@intercom.delete("/subscriptions/#{id}", "")
  end

  def ping(id)
    #ping subscription to test webhook
    @@intercom.post("/subscriptions/#{id}/ping", "")
  end
end

Then you can issue a ping like this to test your webhook:

web.ping("nsub_1b7d84f0-16a3-11e6-aa08-ed65c3040c0f")

Signed Notifications

One feature that you can also use is the ability to request Intercom to sign your notifications. This way you can provide a secret which we pass in the request that you can check to ensure the notification originated in Intercom. You can read more about it here.

The first thing we need to do is to add an option to pass in some text that acts as a secret when you create the webhook. Let's add another parameter to the create method which we can pass through when we want to add a secret to the webhook. We will default it to false so that we don't need to add anything unless we want to include a secret. We talk more about the hub_secret parameter here.

You could tidy up this method to add in the new option but for now let's just add in an extra check in each case, i.e. whether it is an event webhook or standard webhook, to check if the hub secret is set:

  def create_webhook(url, topics, events=false, signed=false)
    if events
      event_data = {
          service_type: "web",
          topics: ["event.created"],
          url: url,
          metadata: {
          event_names: events
          }
      }
      #Check if user want to sign request
      if signed
        event_data[:hub_secret] = signed;

      end
      #Need to POST event metadata for event.created topic
      @@intercom.post("/subscriptions/", event_data)
    else
      #Create a hash to pass through the Webhook data
      webhook = {:url => url,
                 :topics => topics}
      if signed
        webhook[:hub_secret] = signed;
      end
      @@intercom.subscriptions.create(webhook)
    end
  end
require './intercom_client'

class WebhookTasks < IntercomClient
  def initialize
    #Leave this empty as simple over-ride
  end

  def create_webhook(url, topics, events=false, signed=false)
    if events
      event_data = {
          service_type: "web",
          topics: ["event.created"],
          url: url,
          metadata: {
          event_names: events
          }
      }
      #Check if user want to sign request
      if signed
        #events[hub_secret] = ["#{signed}"];
        event_data[:hub_secret] = signed;
      end
      #Need to POST event metadata for event.created topic
      @@intercom.post("/subscriptions/", event_data)
    else
      #Create a hash to pass through the Webhook data
      webhook = {:url => url,
                 :topics => topics}
      if signed
        webhook[:hub_secret] = signed;
      end
      @@intercom.subscriptions.create(webhook)
    end
  end

  def update_webhook(id, url, new_topics, events=false)
    #Create the data you are going to POST
    updates = {
        topics: new_topics,
        url: url,
        }
    if events
      updates[:metadata] = {event_names: events}
    end
    @@intercom.post("/subscriptions/#{id}", updates)
  end

  def list_subscriptions(attrib=false)
    #Check if there is an attribute specified
    if attrib
      #If there is then assume just this attribute is needed
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.send(attrib.to_sym)}"};
    else
      #Print out default of most useful attributes
      @@intercom.subscriptions.all.each {|sub| puts "#{sub.id}, #{sub.url}, #{sub.active}, #{sub.topics}"};
    end
  end

  def get_subscription(id)
    #The ID is the only unique element of the webhook
    @@intercom.subscriptions.find(:id => id)
  end

  def delete_subscriptions(id)
    #Delete single subscription given Subscription ID
    #We dont have any query parameters so 2nd function param is empty string
    @@intercom.delete("/subscriptions/#{id}", "")
  end

  def ping(id)
    #ping subscription to test webhook
    @@intercom.post("/subscriptions/#{id}/ping", "")
  end
end

The second thing we need to dois to update out Sinatra code to check when this option is set. When it is we need to generate a new hash and check if they match. We are setting the secret here to be "HELLO" and this will be hardcoded into your code. This is fixed when you pass the option through when you create the webhook. So if you set it to something else you will need to change it here in the code to match what you have set:

require 'sinatra'
require 'json'

set :bind, '0.0.0.0'

post '/webhook' do
  body = request.body.read
  push = JSON.parse(body)
  puts "Webhook JSON Data: #{push.inspect}"
  verify_signature(body)
end

def verify_signature(payload_body)
  secret = "HELLO"
  expected = request.env['HTTP_X_HUB_SIGNATURE']
  if expected.nil? || expected.empty? then
    puts "Not signed. Not calculating"
  else
    signature = 'sha1=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), secret, payload_body)
    puts "Expected  : #{expected}"
    puts "Calculated: #{signature}"
    if Rack::Utils.secure_compare(signature, expected) then
      puts "   Match"
    else
      puts "   MISMATCH!!!!!!!"
      return halt 500, "Signatures didn't match!"
    end
  end
end

Now you can test your new signed notification by creating a new webhook and then sending a ping request. Note you will need to restart your Sinatra server after making the above change.

>web.create_webhook('http://9382d4ee.ngrok.io/webhook', ["user.created"], events=false, signed="HELLO")

You should then see output like this in the terminal where your Sinatra server is running:

52.87.211.165 - - [13/May/2016:13:00:03 +0000] "POST /webhook HTTP/1.1" 200 - 0.0036
Webhook JSON Data: {"type"=>"notification_event", "app_id"=>"<APP ID>", "data"=>{"type"=>"notification_event_data", "item"=>{"type"=>"ping", "message"=>"something something interzen"}}, "links"=>{}, "id"=>nil, "topic"=>"ping", "delivery_status"=>nil, "delivery_attempts"=>1, "delivered_at"=>0, "first_sent_at"=>1463145837, "created_at"=>1463145837, "self"=>nil}
Expected  : sha1=846db3ec80a138d191cffda086e36d1b0daf425e
Calculated: sha1=846db3ec80a138d191cffda086e36d1b0daf425e
   Match
54.172.151.69 - - [13/May/2016:13:23:57 +0000] "POST /webhook HTTP/1.1" 200 - 0.0009

Great, it's a match! So you know it is Intercom sending the notification. You can change the secret here and test whether it matches or not or create new webhooks and test them via the ping.

You should now be ready to setup your webhooks to receive notifications relating to your customers and the actions they are taking. Have fun!

Webhooks