Webhooks

Webhooks enable Help Scout to call a script on your server when one or more events have happened. Webhooks can be thought of as event listeners or push notifications.

Configuring webhooks can be done through the Help Scout user interface. Simply log in, click on Manage > Apps in the top header bar, then select the Webhooks app.

Available Events

Each request body uses the object format linked in the table below.

Type Event Name Request Header v2 Request Body
Conversation Assigned convo.assigned v2 Conversation object
Created convo.created v2 Conversation object
Deleted convo.deleted {id: id-of-convo-that-was-deleted}
Merged convo.merged v2 Conversation object
Moved convo.moved v2 Conversation object
Status Updated convo.status v2 Conversation object
Tags Updated convo.tags v2 Conversation object
Custom Fields Updated convo.custom-fields v2 Conversation object
Customer Reply convo.customer.reply.created v2 Conversation object
Agent Reply convo.agent.reply.created v2 Conversation object
Note Created convo.note.created v2 Conversation object
Customer Created customer.created v2 Customer object
Updated customer.updated v2 Customer object
Deleted customer.deleted {id: id-of-customer-that-was-deleted}
Ratings Rating Received satisfaction.ratings v2 Rating object
Beacon Chat Created beacon.chat.created Chat object
Customer Replied beacon.chat.customer.replied Chat object
Tag Created tag.created v2 Tag object
Updated tag.updated v2 Tag object
Deleted tag.deleted {id: id-of-tag-that-was-deleted}
Message Survey Response Received message.survey.response.received Survey Response object

Please note that the customer.created webhook event is not filterable across specific inboxes, and will trigger on all customers created globally.

Headers

Each webhook includes two Help Scout headers:

  • X-HelpScout-Event: Lists the event name for which this webhook event is being generated
  • X-HelpScout-Signature: The computed signature generated by Help Scout. Used to know if the request is valid or not.

Verifying

Webhooks can be verified as coming from Help Scout by calculating a digital signature. Each webhook request contains an X-HelpScout-Signature header, which is generated using the given secret key, along with the json encoded payload data sent in the request.

To verify if the request came from Help Scout, compute the HMAC hash and compare it to the header value sent in the request. If the computed signatures match, you can be sure the request was sent from Help Scout.

Signatures are calculated based on the raw request body passed to your servers by Help Scout. This means that if non-ASCII characters are contained in the payload, you will need to calculate the signature based on the escaped, transliterated string passed to you by Help Scout. We recommend this as best practice in general, even for those primarily working with ASCII data.

PHP

<?php
define('WEBHOOK_SECRET_KEY', 'my-secret-key');

function isFromHelpScout($data, $signature) {
	$calculated = base64_encode(hash_hmac('sha1', $data, WEBHOOK_SECRET_KEY, true));
	return $signature == $calculated;
}

$signature = $_SERVER['X-HELPSCOUT-SIGNATURE'];
$data = file_get_contents('php://input');
if (isFromHelpScout($data, $signature)) {
	// do something
}
?>

When using the Help Scout PHP library, this validation is automatically handled for you.

C#

Thanks to Chris Burch for their C# example code.

publicclassHelpScoutAuthorization

{

  privatestring _secret;

  publicHelpScoutAuthorization(string secret)

  {

    _secret = secret;

  }

  ///<summary>

  /// Use signature to validate this message is from HelpScout

  ///</summary>

  ///<param name="jsonRequestBody">The JSON body</param>

  ///<param name="signature">Typically from Request.Headers["X-HelpScout-Signature"]</param>

  publicbool IsFromHelpScout(string jsonRequestBody, string signature)

  {

    return (HashHmac(jsonRequestBody, _secret) == signature);

  }

  privatestring HashHmac(string message, string secret)

  {

    System.Text.Encoding encoding = System.Text.Encoding.UTF8;

    using(var hmac = new System.Security.Cryptography.HMACSHA1(encoding.GetBytes(secret)))

    {

      var messageByteArray = encoding.GetBytes(message);

      var hash = hmac.ComputeHash(messageByteArray);

      return System.Convert.ToBase64String(hash);

    }

  }

}

Ruby

Thanks to our friends at Parse.com for the Ruby example below.

Thanks to Leo Arnold at Givve for sending us a nice Gist with a Ruby vulnerability fix.

#!/usr/bin/ruby -rrubygems
#
require 'base64'
require 'hmac-sha1' # gem install ruby-hmac
require 'rack/utils' # gem install rack

WEBHOOK_SECRET_KEY = "your secret key"

# Returns true if the webhook request is verified to have come from Help Scout.
#
# data       String  The data posted by Help Scout to the webhook.
# signature  String  Value for the http-x-helpscout-signature header.
#
def is_from_help_scout(data, signature)
  hmac = HMAC::SHA1.new(WEBHOOK_SECRET_KEY)
  hmac.update(data)
  Rack::Utils.secure_compare(Base64.encode64("#{hmac.digest}").strip, signature.strip)
end

# Usage Example
signature = "+oNIxipGoqx4t2BmkBHbXKc6VHM="
data = '{"ticket":{"id":"1","number":"2"},"customer":{"id":"1","fname":"Jackie","lname":"Chan","email":"jackie.chan@somewhere.com","emails":["jackie.chan@somewhere.com"]}}'

puts "This request came from Help Scout:"
puts is_from_help_scout(data, signature)

And an updated ruby example using openssl (instead of hmac-sha1):

#!/usr/bin/ruby -rrubygems

require 'openssl'
require 'base64'
require 'rack/utils'

WEBHOOK_SECRET_KEY = "your secret key"

def is_from_help_scout?(data, signature)
  return false if data.nil? || signature.nil?
  digest = OpenSSL::HMAC.digest('sha1', WEBHOOK_SECRET_KEY, data)
  Rack::Utils.secure_compare(Base64.encode64("#{digest}").strip, signature.strip)
end

data = '{"ticket":{"id":"1","number":"2"},"customer":{"id":"1","fname":"Jackie","lname":"Chan","email":"jackie.chan@somewhere.com","emails":["jackie.chan@somewhere.com"]}}'
signature = request.headers['X-Helpscout-Signature'] # or use signature = 'I1KlvGppYqvFTJgJ9jezdQMDiyI=' which is an actual signature of the data above

puts "This request came from Help Scout:"
puts is_from_help_scout?(data, signature)

Node.js:

Thanks to one of our Help Scout customers for the following Node.js example.

/*jshint node: true */
'use strict';

var crypto = require('crypto'),
config = require('../lib/config').config;

module.exports = function(secret) {
    return function(req, res, next) {
        req.isHelpScout = false;

        var hsSignature = req.header('X-HelpScout-Signature');
        if (hsSignature) {
            req.hsHasher = crypto.createHmac('sha1', secret);
            req.on('data', function (chunk) {
                req.hsHasher.update(chunk);
            });
            req.on('end', function() {
                var hash = req.hsHasher.digest('base64');
                req.isHelpScout = hash === hsSignature ? true : false;
            });
        }
        next();
    };
};

Java

A java example can be found in the Webhook.java class in the Help Scout Java library.

Responses

Anything returned in the body of the response will be discarded. In order to know the webhook was successful, an HTTP status code between 200 and 299 must be returned.

A status code of 410 will cause the webhook to get deactivated/deleted.

Any status code other than something between 200 and 299 or 410 is a failure of some kind. A failed event is retried up to 10 times (with an increasing timeout period in between each retry). If the event fails all 10 retries, it is discarded.

Webhooks are automatically deactivated if ten or more events get discarded.

Note: The Agent Reply event will only return data for User-generated threads. Auto Reply messages are not included in this event.