Loading...

Our Intercom API Integration using Ruby as an Example

I asked our CTO, Joe d'Elia, to cover how the Intercom API integration with our app is done using Ruby. Below we cover the Rails job that syncs data between our app and Intercom including code and comments.
Related: See Intercom Related Hacks and Lessons Learned Here
Upscope is a co-browsing (one click to see the customer's screen) service and we use Intercom for live chat support.
While supporting customers we need to have Intercom synced with our app so we so we can quickly help customers.
Why?
We need to sync up custom attributes so we can add a link in Intercom directly to the admin page for that customer, see their monthly app usage figures, see when they subscribed and more.
While the Intercom widget integration is a simple copy and paste, it's the database sync between Intercom and our database that requires some work.
We want Intercom to be up to date with the latest data from our database that has changed during their interactions with Upscope.

Below you'll see the Rails job that:

  1. Creates a new user or updates an existing user in Intercom.
  2. Updates which teams they belong to as one user can be part of several teams.
  3. Updates their timezone as sometimes Intercom has better information on that.
  4. Updates their name when Intercom has it but we don't as it's manually inputted.
  5. Updates their phone number, which again might be manually inputted.
  6. Updates Intercom attributes using data from our database.
  7. Merges a lead into a user.
class SyncWithIntercomJob < ApplicationJob  queue_as :crm_sync  # Creates a user in intercom if it does not exist. If it does exist, it updates it  def perform(user)    return if ENV['INTERCOM_ACCESS_TOKEN'].nil?    return unless ENV['INTERCOM_SYNC_ENABLED'].true?    # Gets time zone to reset so we know which it is in case we change it    old_time_zone = Time.zone    # Creates the intercom client with that access token    intercom = Intercom::Client.new(token: ENV['INTERCOM_ACCESS_TOKEN'], handle_rate_limit: true)    intercom_user = begin    				  # tries to find user with this id                      intercom.users.find(user_id: user.id)                     # throws exception                      rescue Intercom::ResourceNotFound                      # creates one with that user id                      intercom.users.create(email: user.email, user_id: user.id)                    end	# We store intercom id in our db    user.update metadata: { intercom_user_id: intercom_user.id }    # Sometimes intercom knows time zone of user and we don't so we use their information on the time zone    user_time_zone = intercom_user.try(:location_data).try(:timezone)    if user_time_zone.present?      begin        # Updates timezone to user        Time.zone = user_time_zone        # Saves timezone        user.update! timezone: user_time_zone      rescue ArgumentError        logger.error "Ignored invalid timezone #{user_time_zone}"      end    end    # Adds the teams the user belongs to, remove ones in intercom they no longer belong to as a user can be part of multiple teams on Upscope    intercom_user_companies_ids = intercom_user.companies.map(&:company_id)    user_teams_ids = user.teams.map(&:id)    intercom_user.companies = (intercom_user_companies_ids + user_teams_ids).uniq.map do |id|      {        company_id: id,        remove: !id.in?(user_teams_ids) || nil      }    end    # Sometimes they sign up but we don't have their name but sometimes we add it manually    if user.name?      # If we have the name of the person in Upscope, set to intercom      intercom_user.name = user.name    elsif intercom_user.name.present? && intercom_user.name.length > 1 && !user.from_oauth?      # If we have the name in intercom but not Upscope, update Upscope      user.update name: intercom_user.name    else      # Set intercom name to nil      intercom_user.name = nil    end    # Grab phone number from intercom. If phone number is manually added to intercom, we grab it    if user.phone_number.nil? && intercom_user.phone.present? && !user.from_oauth?      user.update phone_number: intercom_user.phone      user.reload    end    # Here we are updating Intercom from our records    intercom_user.email = user.email    intercom_user.phone = user.phone_number    intercom_user.created_at = user.created_at    # Make sure that, in both intercom and our app, the last request is up to date    user.last_request_at = [user.last_request_at, intercom_user.last_request_at].compact.max    intercom_user.last_request_at = user.last_request_at    # Add the custom attributes for that user to intercom, from our database    intercom_user.custom_attributes = user.as_json(with_everything: true,                                                   except: [:name, :email, :phone_number, :id, :created_at, :last_request_at, :_type])                                          .merge(deleted: false)    intercom.users.save(intercom_user)    # Merge lead if exists. Sometimes there's a lead in intercom which is not a user.If the user was created in the last 24 hours, it looks for a contact with the same email address and merges it.    if user.created_at > 24.hours.ago      # Find lead      begin        intercom_lead = intercom.contacts.find_all(email: user.email).first        intercom.contacts.convert(intercom_lead, intercom_user) if intercom_lead        logger.info 'Merged contact into user'      rescue Intercom::ResourceNotFound        nil      end    end    user.owned_teams.each do |team|      intercom_company = begin                           intercom.companies.find(company_id: team.id)                         rescue Intercom::ResourceNotFound                           intercom.companies.create(company_id: team.id)                         end      intercom_company.name = team.domain      intercom_company.created_at = team.created_at      intercom_company.monthly_spend = team.monthly_spend      intercom_company.custom_attributes = team.as_json(        with_everything: true, except: [:created_at, :id, :_type]      ).merge(deleted: false)      intercom.companies.save(intercom_company)      team.update metadata: { intercom_company_id: intercom_company.id }    end  ensure    Time.zone = old_time_zone  endend

Which attributes do we sync up that you might find useful?

Admin link from Intercom to your own website's admin section.
We use this every day, many times a day. When an existing customer asks a question, we need to see their account to understand their context in full. We do this by clicking the Admin link in the Intercom custom attributes list.

Upscope's own instant no-download interactive screen sharing link
When a non-tech customer needs help it's difficult so Upscope built one click no-download interactive screensharing.
You can see what they see instantly AND scroll and click for them on their screen.
Add this using our own Upscope, which is an official app on the intercom app store.

Annual savings attribute so you can remind them to switch
This custom attribute might for example say: Annual saving $316
If they’ve been on a monthly plan for a while, use this during a chat to remind them of savings they'll make by switching to an annual plan.
Other attributes?
You can see the above and other ideas for Intercom attributes listed here.

Add our JS snippet and see the customer's screen from Intercom

Here's a little bit more info on Upscope which not only uses Intercom but also integrates with it.
While chatting, you can click on a link to instantly, without downloads, see what the customer sees and click for them.
We've been tracking feedback and it cuts resolution time by 43% on average though we tend to quote 30% as a good minimum.
To add Upscope to Intercom you add the Upscope javascript snippet on the same page as the Intercom snippet and you have ready to go instant screen sharing.
Start a trial here and if you have questions or need advice, you can ask us on our Intercom live chat. If it's Ruby / API related, Joe's happy to respond.
If you want to see a set of Intercom hacks and lessons learned then see our experience in these articles.

Sunglasses emoji. Continue reading the blog