API & Webhooks Module

API

API Documentation is available here.

Accessing API from JavaScript

It is possible to access API from JavaScript. In “Manage » Settings » API & Webhooks” you can specify the value for ‘Access-Control-Allow-Origin’ header.

Troubleshooting API

API response Status Codes are listed in the documentation 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/json
Keep-Alive: timeout=30
Connection: keep-alive

{
  "id" : 1,
  "number" : 3,
  "threadsCount" : 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",
    "firstName" : "John",
    "lastName" : "Doe",
    "email" : "johndoe@example.org"
  },
  "createdBy" : {
    "id" : 11,
    "type" : "customer",
    "email" : "customer@example.org"
  },
  "createdAt" : "2020-03-15T22:46:22Z",
  "updatedAt" : "2020-03-15T22:46:22Z",
  "closedBy" : 14,
  "closedByUser" : {
    "id" : 14,
    "type" : "user",
    "firstName" : "John",
    "lastName" : "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" ],
  "customer" : {
    "id" : 91,
    "type" : "customer",
    "firstName" : "Rodney",
    "lastName" : "Robertson",
    "photoUrl" : "https://support.example.org/storage/customers/7a10629fd2bae86563892b191f6677e7.jpg",
    "email" : "rodney@example.org"
  },
  "customFields" : [ {
      "id": 22,
      "name": "Amount",
      "value": "7",
      "text": ""
    }, {
      "id": 23,
      "name": "Currency",
      "value": "1",
      "text": "USD"
  } ],
  "_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",
        "firstName" : "Rodney",
        "lastName" : "Robertson",
        "photoUrl" : "https://support.example.org/storage/customers/7a10629fd2bae86563892b191f6677e7.jpg",
        "email" : "rodney@example.org"
      },
      "createdBy" : {
        "id" : 91,
        "type" : "customer",
        "firstName" : "Rodney",
        "lastName" : "Robertson",
        "photoUrl" : "https://support.example.org/storage/customers/7a10629fd2bae86563892b191f6677e7.jpg",
        "email" : "rodney@example.org"
      },
      "assignedTo" : {
        "id" : 14,
        "type" : "user",
        "firstName" : "John",
        "lastName" : "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/json
Keep-Alive: timeout=30
Connection: keep-alive

{
  "id" : 75,
  "firstName" : "Mark",
  "lastName" : "Morrison",
  "company" : "Example, Inc",
  "jobTitle" : "Secretary",
  "photoType" : "unknown",
  "photoUrl" : "https://support.example.org/storage/customers/7a10629fd2bae86563892b191f6677e7.jpg",
  "createdAt" : "2020-07-23T12:34:12Z",
  "updatedAt" : "2020-07-24T20:18:33Z",
  "notes" : "Nothing special to say.",
  "customerFields": [ {
     "id": 11,
     "name": "Age",
     "value": "25",
     "text": ""
  }, {
    "id": 2,
    "name": "Gender",
    "value": "1",
    "text": "Male"
  } ],
  "_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",
      "zip" : "123123",
      "country" : "US",
      "address" : "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.

Troubleshooting

404 error when making an API call: “Sorry, the page you are looking for could not be found”

Make sure that you have this API & Webhooks module installed and activated.

Webhooks sends an empty body

Try to delete the webhook and create again.

Webhook is not called

If you are using CloudFlare make sure it’s not blocking module’s request (check CloudFlare logs: “Security » Events” and scroll down to “Activity log”.).

Also Webhooks have their own logs on Webhooks page – make sure to check them. Also  check that your background jobs are running: https://github.com/freescout-helpdesk/freescout/wiki/Background-Jobs

And here is the tool which may help to check Webhook requests: https://webhook.site

n8n service receives an empty body in the Webhook

See this instruction.

Lifetime license for one FreeScout instance
Purchase
  • Version: 1.0.78
  • Required App Version: 1.8.120
  • Required PHP Extensions: hash
  • License Period: Lifetime
  • Open Source: AGPL-3.0