API & Webhooks Module

API

For now this module provides the following API methods: Get Conversation, Create Customer, Get Customer. More API methods will be added with time.

Full API Documentation is available here.

Webhooks

Webhooks are like event listeners, they allow you to call a script (URL) on your server when specific events happen in FreeScout. When the event occurs, FreeScout sends a POST request to the provided URL.

In order to know the webhook was successful, your script must return an HTTP status code between 200 and 299. Anything returned in the body of the response is discarded. A failed event is retried up to 10 times (with an increasing timeout period in between each retry during 2 hours). Logs are preserved for 3 days.

This module requires PHP “hash” extension to sign webhook requests.

Available Events

Event Description Request Body
CONVERSATION
convo.assigned Conversation assigned Conversation object
convo.created Conversation created Conversation object
convo.deleted Conversation deleted Conversation object
convo.moved Conversation moved Conversation object
convo.status Conversation status updated Conversation object
convo.customer.reply.created Customer replied Conversation object
convo.agent.reply.created Agent replied Conversation object
convo.note.created Note added Conversation object
CUSTOMER
customer.created Customer created Customer object
customer.updated Customer updated Customer object

Example of Conversation object:

HTTP/1.1 200 OK
Content-Type: application/hal+json
Keep-Alive: timeout=30
Connection: keep-alive

{
  "id" : 1,
  "number" : 3,
  "threads" : 2,
  "type" : "email",
  "folderId" : 11,
  "status" : "closed",
  "state" : "published",
  "subject" : "Refund",
  "preview" : "Could you please refund my recent payment...",
  "mailboxId" : 15,
  "assignee" : {
    "id" : 9,
    "type" : "user",
    "first" : "John",
    "last" : "Doe",
    "email" : "johndoe@example.org"
  },
  "createdBy" : {
    "id" : 11,
    "type" : "customer",
    "email" : "customer@example.org"
  },
  "createdAt" : "2020-03-15T22:46:22Z",
  "closedBy" : 14,
  "closedByUser" : {
    "id" : 14,
    "type" : "user",
    "first" : "John",
    "last" : "Doe",
    "photoUrl" : "https://support.example.org/storage/users/5a10629fd2bae86563892b191f6677e7.jpg",
    "email" : "johndoe@example.org"
  },
  "closedAt" : "2020-03-16T14:07:23Z",
  "userUpdatedAt" : "2020-03-16T14:07:23Z",
  "customerWaitingSince" : {
    "time" : "2020-07-24T20:18:33Z",
    "friendly" : "10 hours ago",
    "latestReplyFrom" : "customer"
  },
  "source" : {
    "type" : "email",
    "via" : "customer"
  },
  "cc" : [ "fox@example.org" ],
  "bcc" : [ "fox@example.org" ],
  "primaryCustomer" : {
    "id" : 91,
    "type" : "customer",
    "first" : "Rodney",
    "last" : "Robertson",
    "photoUrl" : "https://support.example.org/storage/customers/7a10629fd2bae86563892b191f6677e7.jpg",
    "email" : "rodney@example.org"
  },
  "_embedded" : {
    "threads" : [ {
      "id" : 17,
      "type" : "customer",
      "status" : "active",
      "state" : "published",
      "action" : {
        "type" : "changed-ticket-assignee",
        "text" : "John Doe assigned conversation to Mark",
        "associatedEntities" : { }
      },
      "body" : "Thank you very much!",
      "source" : {
        "type" : "email",
        "via" : "customer"
      },
      "customer" : {
        "id" : 91,
        "type" : "customer",
        "first" : "Rodney",
        "last" : "Robertson",
        "photoUrl" : "https://support.example.org/storage/customers/7a10629fd2bae86563892b191f6677e7.jpg",
        "email" : "rodney@example.org"
      },
      "createdBy" : {
        "id" : 91,
        "type" : "customer",
        "first" : "Rodney",
        "last" : "Robertson",
        "photoUrl" : "https://support.example.org/storage/customers/7a10629fd2bae86563892b191f6677e7.jpg",
        "email" : "rodney@example.org"
      },
      "assignedTo" : {
        "id" : 14,
        "type" : "user",
        "first" : "John",
        "last" : "Doe",
        "photoUrl" : "https://support.example.org/storage/users/5a10629fd2bae86563892b191f6677e7.jpg",
        "email" : "johndoe@example.org"
      },
      "to" : [ "test@example.org" ],
      "cc" : [ "fox@example.org" ],
      "bcc" : [ "fox@example.org" ],
      "createdAt" : "2020-06-05T20:18:33Z",
      "openedAt" : "2020-06-07T10:01:25Z",
      "_embedded" : {}
    } ]
  }
}

Example of Customer object:

HTTP/1.1 200 OK
Content-Type: application/hal+json
Keep-Alive: timeout=30
Connection: keep-alive

{
  "id" : 75,
  "firstName" : "Mark",
  "lastName" : "Morrison",
  "gender" : "male",
  "jobTitle" : "Secretary",
  "location" : "1419 Westwood Blvd",
  "organization" : "Example, Inc",
  "photoType" : "unknown",
  "photoUrl" : "https://support.example.org/storage/customers/7a10629fd2bae86563892b191f6677e7.jpg",
  "age": "27",
  "createdAt" : "2020-07-23T12:34:12Z",
  "updatedAt" : "2020-07-24T20:18:33Z",
  "background" : "Nothing special to say.",
  "_embedded" : {
    "emails" : [ {
      "id" : 1,
      "value" : "mark@example.org",
      "type" : "home"
    } ],
    "phones" : [ {
      "id" : 0,
      "value" : "777-777-777",
      "type" : "home"
    } ],
    "social_profiles": [ {
      "id" : 0,
      "value" : "@markexample",
      "type" : "twitter"
    } ],
    "websites" : [ {
      "id" : 0,
      "value" : "https://example.org"
    } ],
    "address" : {
      "city" : "Los Angeles",
      "state" : "California",
      "country" : "US",
      "lines" : [
        "1419 Westwood Blvd"
      ]
    }
  }
}

Headers

Each webhook includes the following FreeScout headers:

  • X-FreeScout-Event: Name of the event for which this webhook has been triggered.
  • X-FreeScout-Signature: The generated signature used to know if the request comes from your FreeScout.

Verifying Signature

Each webhook request contains an X-FreeScout-Signature header, which is generated using the secret key (you can get it in “Manage » API & Webhoks”). To make sure that the request came from FreeScout, 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 your FreeScout. Signatures are calculated based on the raw request body passed to your servers by FreeScout.

PHP

define('WEBHOOK_SECRET_KEY', '18dc4eb09528f9572bed10b5491000fc');

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

$signature = $_SERVER['HTTP_X_FREESCOUT_SIGNATURE'] ?? '';
$data = file_get_contents('php://input');

if (isFromFreeScout($data, $signature)) {
    // do something
}

RUBY

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

WEBHOOK_SECRET_KEY = "18dc4eb09528f9572bed10b5491000fc"

def is_from_free_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 = '{"id" : 123,"number" : 12,"threads" : 2}'

puts is_from_free_scout(data, signature)

Instead of ‘hmac-sha1’ openssl can be used:

require 'openssl'
...
digest = OpenSSL::HMAC.digest('sha1', WEBHOOK_SECRET_KEY, data)

NODE.JS

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

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

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

        var hsSignature = req.header('X-FreeScout-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.isFreeScout = hash === hsSignature ? true : false;
            });
        }
        next();
    };
};

JAVA

See this code snippet.

Lifetime license for one domain
Purchase
  • Version: 1.0.1
  • Required App Version: 1.6.2
  • Required PHP Extensions: hash
  • Open Source: AGPL-3.0