Wix Answers Help Center

Webhooks

Creating Webhooks

Creating a WebhookWix Answers enables you to configure webhooks in your account for certain events, like when a ticket is created or when a user replies to a ticket. When triggered, Wix Answers sends a POST request to the webhook URL that you've defined. To create a webhook:Hover over Settingsand click Webhooks & API Keys. Click Add Webhook. Create a new webhook:Enter a name for your webhook. Enter a URL to where the POST requests are sent. This URL should be configured to take an action based on the data that is posted to it by the webhook.Click the drop-down and choose an action to trigger the event see next section for details):Ticket Created - Triggered after a ticket is createdTicket Transferred - Triggered after an agent transfers a ticket to another userTicket Status Changed - Triggered after the status changes for one or more ticketsReply Created - Triggered after a user creates a replyInternal Note Created - Triggered after an agent adds an internal noteSSO Outdated - Triggered after a user's email address is updated automatically and this results in a conflict: a non-unique email address. Email addresses must be unique in Wix Answers. This webhook is relevant only for accounts that are configured using SSO login.Chat Ended - Triggered when a chat endsCall Ended - Triggered when a call endsCustomer Created or Updated - Triggered when a user is created or modified (not yet available in the Knowledge Center)Article Sent to Translation - Triggered when an article translation task is created (not yet available in the Knowledge Center). An article translation task is created by default when an article translation is created.Company Created / Company Updated / Company Deleted - Triggered when the relevant change is made to a company (not yet available in the Knowledge Center).Webhook TypesThis section presents more information, including payloads for the webhook types.Ticket Created WebhookA Ticket Created webhook is triggered after a ticket is created.For more details about the webhook body, see the Ticket object.content-type: application/json; charset=utf-8Example JSON of posted data sent when a ticket is created:{ "payload": { @Ticket object }, "url": "https://your.endpoint/for/webhook" }Ticket Transferred WebhookA Ticket Transferred webhook is triggered after an agent transfers a ticket. An agent typically transfers a ticket from an unauthenticated user to an authenticated user once the user's ID is known.For more details about the webhook body, see the Ticket object.The @User object contains information about the new user.content-type: application/json; charset=utf-8Example JSON of posted data sent when a ticket is transferred:{ "payload": { @Ticket object }, "url": "https://your.endpoint/for/webhook" }Ticket Status Changed WebhookA Ticket Status Changed webhook is triggered after a ticket's status is changed.  The status is not changed when it is set or when it is cleared, only when it changes from one value to another.For more details about the webhook body, see the Ticket object. The User object contains information about the new user.content-type: application/json; charset=utf-8Example JSON of posted data sent when one or more tickets' statuses change:{ "payload": [ { "ticket": { @Ticket object (with new status) }, "oldStatus": 110 }, ... additional structures if there are other tickets with new statuses ... ], "url": "https://your.endpoint/for/webhook" }Reply Created WebhookA Reply Created webhook is triggered when a user (not an agent) replies to a ticket.The Ticket Object, together with the Reply object, is sent as payload. For more details, see Ticket object and Reply object.content-type: application/json; charset=utf-8Example JSON of posted data sent when a reply is created:{ "payload": { "content": "<div>Here is more information.</div>\n\n", "user": { @User object }, "channel": 110, "creationDate": 1530091629935, "lastUpdateDate": 1530091629935, "id": "ce9d108a-f90a-48ec-8843-e93e97db04ff", "type": 100, "parentTicket": { @Ticket object }, "channelData": { "mailboxId": "e9cb4871-e80b-4969-ab83-3268c8d8ee86", "mailboxEmail": "support.your-name-test@in.wixanswers.com" } }, "url": "https://your.endpoint/for/webhook" }Internal Note Created WebhookAn Internal Note Created webhook is triggered when an agent adds an internal note.The Ticket Object, together with the Reply object, is sent as payload. For more details, see Ticket object and Reply object.content-type: application/json; charset=utf-8Example JSON of posted data sent when an internal note is created:{ "payload": { "content": "<div>Here is more information.</div>\n\n", "user": { @User object }, "channel": 110, "creationDate": 1530091629935, "lastUpdateDate": 1530091629935, "id": "ce9d108a-f90a-48ec-8843-e93e97db04ff", "type": 100, "parentTicket": { @Ticket object }, "channelData": { "mailboxId": "e9cb4871-e80b-4969-ab83-3268c8d8ee86", "mailboxEmail": "support.your-name-test@in.wixanswers.com" } }, "url": "https://your.endpoint/for/webhook" }SSO User Outdated WebhookThis webhook is relevant only for accounts that are configured using SSO login.When a user's email address changes in the external user system, and that user logs into Wix Answers, then the user's email address in Wix Answers is automatically updated to align with the external system. A change like this can cause email conflicts in Wix Answers, because user emails must be unique for each Wix Answers account. If a conflict occurs, Wix Answers triggers this webhook to allow the external user system to resolve the conflict by performing a return API request to Wix Answers to update the user's conflicting email.The User object is sent as a payload. For more details, see User Object.content-type: application/json; charset=utf-8 Example JSON of posted data sent when user's SSO email is outdated:{ "payload": { @User object }, "url": "https://your.endpoint/for/webhook" }Chat Ended WebhookA Chat Ended webhook is triggered when a chat ends. The payload contains the Ticket object, @Chat object, and the User object of the user that ended the chat.content-type: application/json; charset=utf-8Example JSON of posted data sent when a chat ends:{ "payload": { "ticket": { @Ticket object }, "chat": { @Chat object }, "endedBy": { @User object }, "userMessageCount": 2, "agentMessageCount": 3, "userBatchesCount": 2, "agentBatchesCount": 3 }, "url": "https://your.endpoint/for/webhook" }Call Ended WebhookA Call Ended webhook is triggered when a phone call ends or is transferred to an external number (away from Wix Answers). The payload includes the Ticket object and the @Call object.content-type: application/json; charset=utf-8Example JSON of posted data sent when a phone call ends:{ "payload": { "ticket": { @Ticket object }, "phoneCall": { @Call object } }, "url": "https://your.endpoint/for/webhook" }Customer Created or Updated WebhookAPI OnlyThis webhook is available using the API. It is not yet available in the Knowledge Center (UI).A Customer Created or Updated webhook is triggered when a user is created or modified (email, name, and so forth). The payload includes fields of the new user (newUser) or both the old information (oldUser) and the new information (newUser) of the user. The fields include:idemailemailStatuscreationDatelastUpdateDateexternalIduserTypefirstNamelastNamephoneNumberscompanycustomFieldsbannedFor information about these fields, see User Object.content-type: application/json; charset=utf-8Example JSON of posted data sent when a user is modified:{ "payload": { "oldUser": { "id": "3c082d93-0f8b-40ee-bc8a-d77885f2e38b", "email": "3c082d93-0f8b-40ee-bc8a-d77885f2e38b@wixanswersmail.com", "emailStatus": 0, "creationDate": 1580729856000, "lastUpdateDate": 1580730613000, "userType": 2, "firstName": "John", "lastName": "Smith", "banned": false }, "newUser": { "id": "3c082d93-0f8b-40ee-bc8a-d77885f2e38b", "email": "3c082d93-0f8b-40ee-bc8a-d77885f2e38b@wixanswersmail.com", "emailStatus": 0, "creationDate": 1580729856000, "lastUpdateDate": 1580730661000, "userType": 2, "firstName": "Jane", "lastName": "Smith", "banned": false }, }, "url": "https://your.endpoint/for/webhook" }Article Sent to Translation WebhookAPI OnlyThis webhook is available using the API. It is not yet available in the Knowledge Center (UI).An Article Sent to Translation webhook is triggered when your site supports additional languages and an agent sends an article to be translated into one or more languages (locales). The payload includes the Article object and the requested language(s).content-type: application/json; charset=utf-8Example JSON of posted data sent when an article is sent for translation:{ "payload": { "articles": [ { @Article object } ], "translationsLocales": [ locale, locale, ... ] }, "url": "https://your.endpoint/for/webhook" } // locale examples: "fr", "de", ...Company Created / Company Updated / Company Deleted WebhookAPI OnlyThese webhooks are available using the API. They are not yet available in the Knowledge Center (UI).The relevant webhook is triggered when you create / update / or delete a company. The payload includes the Company object.content-type: application/json; charset=utf-8

Setting Up IVR Webhooks

Use an external service to authenticate callers in your IVR flow by setting up a Webhook. Important:This article requires basic knowledge of HTTP servers and Webhooks.What are IVR WebhooksIn some cases we might want to ask our users to enter an identification number so we can know how to route them further on in the IVR flow. IVR Webhooks allow you to use an external service to provide authentication for such cases.How Does It Work?These are the steps that occur when the user reaches a Webhook IVR step:The caller is requested to enter a number followed by # (with a message configured by you).The caller enters the number followed by the # key.A POST HTTP/S request is sent to the external Webhook url defined in IVR settings.External web-hook returns an HTTP/S response.According to the response the caller is either directed to another IVR described by the response, or the call is terminated (more on that in the following sections).Example payload sent to your Webhook:{ callId: 'f304229f-dc2c-4c84-bc65-90c92a980dd7', from: { countryCode: "111", number: "444444444" }, user: { id: "123abc45-67d8-912e-3fg4-56789h0123456", email: "user@domain.com", emailStatus: 0, creationDate: 1540910161000, lastUpdateDate: 1540910161000, userType: 2, firstName: "User", phoneNumbers: [ {countryCode: "111", number: "544444444"} ], banned: false, uri: "/user/123abc45-67d8-912e-3fg4-56789h0123456", fullName: "User User", hasEmail: false, hasName: false, fullPhoneNumbers: [ "111-444444444" ] }, input: "1234" }Hook's ResponseThe response can either be EMPTY or a valid IVR GUID string.Empty responseAn empty response means you are not interested in redirecting the user to another IVR. Thereforeupon receiving an empty response from your Webhook, the call is disconnected.IVR GUID responseYou can return an IVR's GUID as the result from your hook, in which case the ongoing call is redirected to that IVR.Finding an IVR's GUIDFinding the IVR's ID is easy, all you need to do is navigate to the desired IVRand copy the <IVR-GUID> part from the browser's URL:<your-wix-answers-domain>/settings/callcenter/ivrs/<IVR-GUID>Example Use-CaseA common use-case might be as follows:A user (caller) calls your Call Center.The caller is routed to a main menu IVR.The caller chooses an option that requires authentication.The caller is routed to the Number Input IVR.The caller enters his ID number followed by #.A request is sent to your webhook with the user's details and input.Your webhook looks up the user in some DB or Server.If user is found - the webhook checks that the input matches the expected ID.If the ID matches, the webhook returns a response containing the GUID of some success IVR, otherwise it might return the GUID of some failure IVR.7. The user is routed to the IVR returned by the webhook.How you can achieve this in Wix AnswersIn order to achieve the above example, we will need to create 4 IVR flows:Main menu IVR - will act as the entry point for the IVR flow, listing the different options.External webhook IVR - will get input from the user and authenticate against the external service.Success IVR - will be redirected to the external service upon successful authentication.Failure IVR - will be redirected to the external service upon unsuccessful authentication.Having these set up, all that is left to do is create a simple web server that will handle the requests sent to: https://www.external-service.comlet users = [ {id: '602efa31-01d8-482d-8ff2-26910b026703', pass: '1234'}, {id: '602efa31-01d8-482d-8ff2-26910b026703', pass: '4321'}, ] app.post('/', function (req, res) { console.log(req.body) let caller = req.body.user; let input = req.body.input; let dbUser = users.find((user) => user.id === caller.id); if (dbUser) { // Found user, authenticating password if (dbUser.pass === input) { // User entered correct password, routing to success IVR res.send("5a9e9d0d-6d65-4494-ad7e-0ee7bc7aa064"); } else { // User entered wrong password, routing to failure IVR res.send("5a9e9d0d-6d65-4494-ad7e-0ee7bc7aa064"); } } else { // Could not find user, disconnecting call res.send(); } }) app.listen(80, function () { console.log("IVR authentication server example"); console.log("#################################"); })

Securing Webhooks

After you configure webhooks (see Creating Webhooks) and your server to receive those request, you may want to verify that webhook requests come from Wix Answers.Set Webhook Secret or Ensure You Have One TODO: wait for UI. than show to create secretValidate SignatureWhen your webhook secret is set, Wix Answers uses it to create a hash signature with request payload.This hash signature is passed along with each webhook request in the headers as 'X-Answers-Signature'.The goal is to compute a hash using your SECRET, and ensure that the hash from Wix Answers matches. Wix Answers uses an HMAC-SHA256 algorithm to compute the hash. You could use the following code samples to see how to validate a signature:var express = require('express'); var app = express(); var bodyParser = require('body-parser'); app.use(bodyParser.json()); var http = require("http"); var url = require("url"); var crypto = require("crypto"); var moment = require('moment'); var sharedSecret = "YOUR-SHARED-SECRET"; const validateSignature = (retrievedSignature, body) => { // Recalculate signature. var computedSignature = crypto.createHmac("SHA256", sharedSecret).update(body).digest("base64"); console.log("computedSignature " + computedSignature); console.log("retrievedSignature " + retrievedSignature); // Compare signatures. const computed = Buffer.from(computedSignature, 'base64'); const retrieved = Buffer.from(retrievedSignature, 'base64'); const valid = crypto.timingSafeEqual(computed, retrieved); return valid; } const validateTimestamp = (timestamp) => { currentTimestamp = moment().valueOf(); secondsInterval = Math.round((currentTimestamp - timestamp) / 1000); console.log('seconds interval: ' + secondsInterval); return secondsInterval < 1000000; } app.post('/', function(req, res){ var body = JSON.stringify(req.body); // Get signature. var retrievedSignature = req.get('X-Answers-Signature'); const isValidSignature = validateSignature(retrievedSignature, body); const isValidTimestamp = validateTimestamp(req.body.timestamp); if (isValidSignature && isValidTimestamp) { res.writeHead(200, { "content-type": "text/plain" }); res.end("Hello World"); } else { res.writeHead(403, { "content-type": "text/plain" }); res.end("NOPE!"); } }); port = 1337; app.listen(port); console.log('Listening at http://localhost:' + port) Python 2 Example:#python import base64 import hmac import hashlib import time from flask import Flask, abort, request import json app = Flask(__name__) secret ='YOUR-SHARED-SECRET' def validate_signature(body): signature = request.headers['X-Answers-Signature'] print('signature: [' + signature + ']') digest = hmac.new(secret, body, hashlib.sha256).digest() computed_signature = base64.encodestring(digest).strip('\n').strip('\t') print('computed_signature: [' + computed_signature + ']') # NOTICE - Simple string comparisons not secure against timing attacks!! return computed_signature == signature def validate_timestamp(body): json_body = json.loads(body) timestamp = json_body['timestamp'] current_timestamp = int(round(time.time() * 1000)) seconds_interval = (current_timestamp - timestamp) / 1000 print('seconds interval: ' + str(seconds_interval)) # define the desired second interval to prevent repeat attacks return seconds_interval < 10 @app.route('/', methods=['POST']) def foo(): body = request.data is_valid_timestamp = validate_timestamp(body) is_valid_signature = validate_signature(body) return json.dumps(is_valid_signature and is_valid_timestamp) if __name__ == '__main__': app.run(host='0.0.0.0', port=1337, debug=False) Java Example:import org.apache.commons.io.IOUtils; import org.apache.commons.net.util.Base64; import org.joda.time.DateTimeUtils; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class Validator { public static final String SECRET = "YOUR-SECRET-HERE"; public static final String SIGNATURE_HEADER = "X-Answers-Signature"; private final static int WEBHOOK_TTL_MILLIS = 10 * 1000; //allows request sent in the last 10 seconds public void validate(HttpServletRequest request) throws ValidationException, IOException { String sig = request.getHeader(SIGNATURE_HEADER); String body = IOUtils.toString(request.getReader()); long timestamp = extractTimestamp(body) if (!isValidWebhookTTL(dto.getTimestamp())) { throw new ValidationException(); } if (!isValidSignature(requestSignature, SECRET, body)) { throw new ValidationException(); } } private boolean isValidSignature(String sig, String body) { Mac mac = Mac.getInstance(signAlgorithm); mac.init(new SecretKeySpec(SECRET.getBytes(), "HMACSHA256")); byte[] value = mac.doFinal(body.getBytes("UTF-8")) Base64 base64 = new Base64(256, (byte[])null, false); String signature = String(base64.encode(value)); // TODO: not secure against timing attacks!! return signature == requestSignature; } private boolean isValidWebhookTTL(long timestamp) { long currentTimestamp = DateTimeUtils.currentTimeMillis(); return currentTimestamp - timestamp <= WEBHOOK_TTL_MILLIS; } }