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 |
---|---|---|
convo.assigned | Conversation assigned | Conversation object |
convo.created | Conversation created | Conversation object |
convo.deleted | Conversation deleted | Conversation object |
convo.deleted_forever | Conversation deleted forever | Conversation object |
convo.restored | Conversation restored from Deleted folder | 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.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.