GUURU for Developers

GUURU for Developers

  • Docs
  • Contacts

›Webhooks

Chat Button

  • Getting Started
  • Public Interface
  • Handling Events
  • Conversion
  • Cookies
  • Examples

SmartChat

  • Getting started
  • Chat Window

    • Getting started
    • Parameters

SmartForm

  • Getting Started

Admin API

  • Getting started
  • Examples
  • API Reference

    • Queries
    • Mutations
    • Types
    • Pagination

Webhooks

  • Getting started
  • Examples

Integrations

  • Facebook
  • Freshchat
  • Salesforce
  • Zendesk
  • Microsoft
  • Third Party Messaging

Getting started

Webhooks allow you to build or set up internal applications which subscribe to certain events from Guuru. When one of those events is triggered, we will send a HTTP POST payload to the webhook's configured URL.

Initial configuration

To set up a webhook, visit the Partner Portal Developer page under Settings. Each supported event (e.g. Chat Rated) is shown there and at a minimum you should define a URL under your control for each event you are interested in receiving.

When the event occurs, if you have the corresponding webhook set as active and a non empty URL configured, we will send a HTTP POST request to your URL with a Content-Type: application/json encoded body.

Events

The request contains a X-Guuru-Event header which identifies the kind of event that triggered the request.

EventDescription
chat-assignedchat has been assigned to experts so that they accept it
chat-openedan expert is handling the chat
chat-closedchat has been closed and is considered resolved
chat-reopenedexpert or customer reopened the chat to handle a better resolution
chat-transferredguuru transferred the chat to a company representative
chat-ratedcustomer has finished chating and rated the chat
message-createda new message has been sent to a chat

Note that a chat-transferred or chat-closed event will eventually be followed by a chat-rated event as the customer can rate chats after their resolution.

Idempotency

Webhook endpoints might occasionally receive the same event more than once. We advise you to guard against duplicated event receipts by making your event processing idempotent. One way of doing this is logging the events you’ve processed, and then not processing already-logged events. You can handle this by looking at the header Idempotency-Key

Payload structure

The request body is sent in JSON and follows the structures described below (by event).

Fields are omitted when their value is not available.

chat-assigned
message-created
chat-*
{
"id": String,
"requestedExperts": [String],
"category": {
"id": String,
"title": String,
},
"user": {
"id": String,
"name": String,
"email": String,
"locale": String // ISO 3166 2 letter country code
},
"question": String,
"languageCode": String, // ISO 639-1 2 letter language code
"createdAt": Number, // in milliseconds since epoch
"referer": String,
"customMeta": { // any custom keys defined in the chatloader
"customKey": String
// ...
},
"assignedAt": Number, // in milliseconds since epoch
}
{
"chat": {
"id": String,
},
"id": String,
"user": {
"id": String,
},
"expert": {
"id": String,
},
"isUser": Boolean,
"text": String,
"type": String,
"hideForUser": Boolean,
"hideForExpert": Boolean,
"createdAt": Number, // in milliseconds since epoch
"attachment": {
"url": String,
"filename": String,
"mimetype": String,
},
}
{
"id": String,
"user": {
"id": String,
"name": String,
"email": String,
"locale": String // ISO 3166 2 letter country code
},
"expert": {
"id": String,
"name": String,
"email": String,
},
"category": {
"id": String,
"title": String,
},
"question": String,
"languageCode": String, // ISO 639-1 2 letter language code
"status": String,
"rating": Number, // 0, 0.5, or 1
"transcriptURL": String,
"createdAt": Number, // in milliseconds since epoch
"acceptedAt": Number, // in milliseconds since epoch
"closedAt": Number, // in milliseconds since epoch
"ratedAt": Number, // in milliseconds since epoch
"lastOpenedAt": Number, // in milliseconds since epoch
"referer": String,
"customMeta": { // any custom keys defined in the chatloader
"customKey": String
// ...
},
}

Securing your webhooks

Once your server is configured to receive payloads, it will listen for any request sent to the URL you configured. For security reasons, you probably want to limit requests to those coming from Guuru.

You need to set up a secret token (pre-shared key) in two places: Partner Portal and your server.

The pre-shared key shouldn't be used for anything else, i.e., it should not be a password or token that you use for another service. We recommend that you generate a strong random key using openssl rand -hex 20 but any sequence will do.

Once configured, we will start sending a X-Guuru-Hmac-Sha256 header in our HTTP POST requests. This is a signature computed using hash-based message authentication code (HMAC) with SHA-256 from the payload and the secret you provided.

To verify that the request was not tampered with (integrity) and was sent by us (authenticity) use the following steps:

  1. Extract the signature from the header: The signature is sent as the value of a request header with the key X-Guuru-Hmac-Sha256. Note that your programming language or library may expose the header key as a lower case string, i.e., x-guuru-hmac-sha256.

  2. Determine the expected signature: Compute an HMAC with the SHA256 hash function. Use the secret you configured in the Partner Portal as the key, and use the request body as the message and convert to an hex string representation.

  3. Compare signatures: Compare the signature in the header to the expected signature. If they match you can process the request, if they don't reject the request.

In the examples below, we could have used a simple comparison == to check if the computed signature matches the received signature and it would work, however, to protect against timing attacks we recommend using a constant-time string comparison, when available.

The following examples illustrate this.

JavaScript
Python
Ruby
Go
Java
const crypto = require('crypto');

const digest = crypto.createHmac('SHA256', SECRET)
.update(Buffer.from(req.body, 'utf8'))
.digest('hex');

if (crypto.timingSafeEqual(
Buffer.from(digest),
Buffer.from(req.headers['x-guuru-hmac-sha256']),
)) {
// ...
}
import hashlib
import hmac

hash = hmac.new(SECRET, request.data, hashlib.sha256)
digest = hash.hexdigest()

if hmac.compare_digest(digest, request.headers['X-Guuru-Hmac-Sha256']):
# ...
require 'openssl'

hash = OpenSSL::Digest.new('sha256')
digest = OpenSSL::HMAC.digest(hash, SECRET, req.data)

if digest == request.header['x-guuru-hmac-sha256'].join
# ...
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)

hash := hmac.New(sha256.New, []byte("secr3t"))
hash.Write([]byte(body))
digest := hex.EncodeToString(hash.Sum(nil))

if hmac.Equal([]byte(digest), []byte(r.Header.Get("X-Guuru-Hmac-Sha256"))) {
// ...
}
public class Example {
public static String getSignature(String secret, String body) {
String digest;

try {
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
secret.getBytes(),
"HmacSHA256"
);
hmac.init(secretKey);

// hex digest of the hmac signature
digest = String.format(
"%x",
new BigInteger(1, hmac.doFinal(body.getBytes()))
);
} catch (Exception e) {
throw new RuntimeException(e);
}
return digest;
}

// ...
public void example(/* ... */) {
String digest = getSignature("secr3t", body);
if (!MessageDigest.isEqual(
digest.getBytes(),
t.getRequestHeaders().getFirst("x-guuru-hmac-sha256").getBytes()
)) {
// ...
}
}
}

Making a test request

The command below sends an example payload corresponding to a Chat Rated event, that would be sent to the configured URL when the user rates a chat.

You can use it to test your implementation before configuring the webhook.

In the example below the X-Guuru-Hmac-Sha256 is computed with the secret secr3t as an example. Never use this or any other weak secret in production.

curl -X POST -H 'Content-Type: application/json' \
-H 'X-Guuru-Event: chat-rated' \
-H 'X-Guuru-Hmac-Sha256: 661dc72784376f80296f93790146a60d6b703b0faca466ebfaaf783787a47114' \
-d '{
  "user": {
    "email": "john.doe@example.com",
    "locale": "de",
    "name": "John"
  },
  "question": "I did not receive last month bill, how can I pay?",
  "status": "rated",
  "language": "de",
  "transcriptURL": "https://chat.guuru.com/apple/transcripts/-L98hdjh8Ehuhd",
  "rating": 1,
  "category": "general",
  "createdAt": 1533795300000,
  "acceptedAt": 1533795320000,
  "closedAt": 1533795480000,
  "ratedAt": 1533795480000,
  "referer": "https://support.apple.com/en-us/HT204247"
}' <URL>

Retry window

If there is any problem delivering the request to your endpoint, e.g., network problems or your endpoint is down for maintenance, deploys or any other reason, it will not be lost as we will keep retrying.

For any given chat that we are unable to send, we will retry every 10 minutes (600 seconds) for up to 7 days. However, if for any given webhook there are more than 500 failed delivery attempts in any given hour that webhook will be disabled and will have to be re-enabled manually.

We use At-Least-Once delivery semantics, meaning that in some circumstances we may send the same event twice.

You can handle this by using the Idempotency-Key header that is unique per event.

← PaginationExamples →
  • Initial configuration
  • Events
    • Idempotency
  • Payload structure
  • Securing your webhooks
  • Making a test request
  • Retry window
GUURU for Developers
Docs
SmartChatSmartFormAPI Reference
Community
GitLabFacebookLinkedIn
Copyright © 2025 GUURU Solutions Ltd.