Webhooks Examples
In the examples below we implement a simple HTTP server that listens for
connections and verifies the integrity and authenticity of the request using a hash-based message
authentication code (HMAC
) with SHA256
with the secret you define as key and
the request body as message.
To define a secret go to Partner Portal → Settings → Developer, expand
the Webhooks section and type a Secret
. In the examples we use secr3t
but
you should choose a strong, randomly generated password.
Although these are working examples, you should not deploy them directly to production. They are intended to give you a quick working version to help you get started.
Code examples with integrity validation
In the examples below we implement a simple HTTP server in various languages
without any third-party dependencies that receives HTTP POST
requests and
computes the HMAC
with the SHA256
hash function using secr3t
as the key
and the body of the request as the message and compares it against the signature
received in the X-Guuru-Hmac-Sha256
header.
For simplicity the examples below assume the presence of the header
X-Guuru-Hmac-Sha256
but your production code should check for it.
const http = require('http');
const crypto = require('crypto');
const port = 8000;
const requestHandler = (req, resp) => {
let body = [];
req.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
const digest = crypto.createHmac('SHA256', 'secr3t')
.update(Buffer.from(body, 'utf8'))
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(digest),
Buffer.from(req.headers['x-guuru-hmac-sha256']),
)) {
resp.statusCode = 400;
return resp.end('Invalid request, non-matching HMAC-SHA256 received');
}
return resp.end('Success!');
});
};
const server = http.createServer(requestHandler);
server.listen(port, (err) => {
if (err) {
return console.log('Error starting HTTP server', err);
}
console.log(`HTTP server listening on ${port}`);
});
from http.server import BaseHTTPRequestHandler, HTTPServer
import hashlib
import hmac
PORT = 8000
class requestHandler(BaseHTTPRequestHandler):
def do_POST(self):
body_len = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(body_len)
hash = hmac.new(b'secr3t', body, hashlib.sha256)
digest = hash.hexdigest()
if not hmac.compare_digest(digest, self.headers.get('X-Guuru-Hmac-Sha256')):
self.send_response(400)
self.end_headers()
self.wfile.write(b'Invalid request, non-matching HMAC-SHA256 received')
return
self.send_response(200)
self.end_headers()
self.wfile.write(b'Success!')
return
try:
server = HTTPServer(('', PORT), requestHandler)
print('HTTP server listening on', PORT)
server.serve_forever()
except KeyboardInterrupt:
server.socket.close()
require 'openssl'
require 'webrick'
PORT = 8000
class WebhookServer < WEBrick::HTTPServlet::AbstractServlet
def do_POST(request, response)
hash = OpenSSL::Digest.new('sha256')
digest = OpenSSL::HMAC.hexdigest(hash, 'secr3t', request.body())
unless digest == request.header['x-guuru-hmac-sha256'].join
response.status = 400
response.body = 'Invalid request, non-matching HMAC-SHA256 received'
return
end
response.status = 200
response.body = 'Success!'
end
end
server = WEBrick::HTTPServer.new(:Port => 8000)
server.mount '/', WebhookServer
trap 'INT' do server.shutdown end
server.start
package main
import (
"io/ioutil"
"net/http"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func requestHandler(w http.ResponseWriter, r *http.Request) {
body, _ := ioutil.ReadAll(r.Body)
defer r.Body.Close()
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"))) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid request, non-matching HMAC-SHA256 received"))
return
}
w.Write([]byte("Success!"))
}
func main() {
http.HandleFunc("/", requestHandler)
if err := http.ListenAndServe(":8000", nil); err != nil {
panic(err)
}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.security.MessageDigest;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
public class Main {
private final static int PORT = 8000;
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);
server.createContext("/", new RequestHandler());
server.setExecutor(null);
server.start();
}
static class RequestHandler implements HttpHandler {
@Override
public void handle(HttpExchange t) throws IOException {
InputStream is = t.getRequestBody();
String body = new String(is.readAllBytes());
String digest = getSignature("secr3t", body);
String response;
if (!MessageDigest.isEqual(
digest.getBytes(),
t.getRequestHeaders().getFirst("x-guuru-hmac-sha256").getBytes()
)) {
response = "Invalid request, non-matching HMAC-SHA256 received";
t.sendResponseHeaders(400, response.length());
} else {
response = "Success!";
t.sendResponseHeaders(200, response.length());
}
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
private 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);
digest = String.format(
"%x",
new BigInteger(1, hmac.doFinal(body.getBytes()))
);
} catch (Exception e) {
throw new RuntimeException(e);
}
return digest;
}
}
Requests and expected signatures
Below are some example payloads and the expected HMAC-SHA256 signatures for
each, assuming that the token secr3t
is being used.
These examples are JSON without spaces or new lines, just like the payload our webhooks send in the body of each request. For a human friendly example see this test request.
Example 1
{"user":{"name":"joao","email":"","locale":"en"},"question":"My router lights are all yellow and blinking and I can't connect to the Internet from my laptop, what can I do?","status":"rated","language":"en","transcriptURL":"https://chat.guuru.com/guuru-qa/transcripts/-LZu0Bo-008GtlT9A5Oj","rating":1,"category":"general","createdAt":1551456587480,"acceptedAt":1551456623552,"closedAt":1551456680711,"ratedAt":1551456684623}
X-Guuru-Hmac-Sha256: b354f1d1c6586dacd3d578ee59e82d9829761cbe65ba2a9c948f99a34009792a
Example 2
{"user":{"name":"João","email":"","locale":"en"},"question":"Is shipping free for Portugal?","status":"rated","language":"en","transcriptURL":"https://chat.guuru.com/guuru-qa/transcripts/-LZu-ZrIBzpNHGswVFuP","rating":1,"category":"general","createdAt":1551456423860,"acceptedAt":1551456435840,"closedAt":1551456470489,"ratedAt":1551456472882}
X-Guuru-Hmac-Sha256: e3671b73d2c6ef0c0a32cb07db50981ac1eab733632d3915d9a4bebd7454cbeb
Example 3
{"user":{"name":"joao","email":"","locale":"en"},"question":"hello 123","status":"rated","language":"en","transcriptURL":"https://chat.guuru.com/guuru-qa/transcripts/-LZu--MHQ3xLy1RvHqv4","rating":1,"category":"general","createdAt":1551456274349,"acceptedAt":1551456289259,"closedAt":1551456338034,"ratedAt":1551456341040}
X-Guuru-Hmac-Sha256: 7480a83edb7109f9f0021f6150383879f97e926654b91ac3e2aa591dd8fad851