Skip to main content

Webhook Security

In order to secure our communication, we provide a Webhook Secret mechanism which can be used to verify Webhook request whether it is really sent from Tokopedia or there’s a malicious third party pretending to be Tokopedia.

First thing first, you should head here to register your Webhook Secret. Simply fill up secret field to begin with Webhook Security.

Note that if you've already registered Webhook URL via the API above, you would need to provide a copy of your current urls to prevent it being replaced by empty string.

Verifying Payload

To verify the payload, you would have to first understand the few important steps.

  1. First, you would have to read the request body, this process may vary between languages.
  2. Then, encode the request body received with your webhook secret key using SHA-256 Algorithm.
  3. Using the recently generated value, you should encode it to hexadecimal.
  4. Lastly, you should compare your encoded value with our value in Authorization-Hmac header.

Here's the code example provided for select programming languages below.

This Golang example uses Golang Standard Library, if you’re unfamiliar with how Golang works, we recommend reading the official documentation here.

package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io/ioutil"
"log"
"net/http"
)

const YourWebhookSecret string = "YOUR_KEY"

func main() {
http.HandleFunc("/listener", func(w http.ResponseWriter, req *http.Request) {
// Read the request body
body, _ := ioutil.ReadAll(req.Body)

// Encrypt the request body with your webhook secret
mac := hmac.New(sha256.New, []byte(YourWebhookSecret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))

// Compare our HMAC with your HMAC
if req.Header.Get("Authorization-Hmac") != expected {
log.Print("Failed to verify!")
return
}
log.Print("Successfully verified!")
})
log.Print("Server is running!")
log.Fatal(http.ListenAndServe(":8080", nil))
}

This Java example uses Spring Boot, if you’re unfamiliar with how Spring Boot works, we recommend reading the official documentation here.

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

@SpringBootApplication
@RestController
public class DemoApplication {
static final String YOUR_WEBHOOK_SECRET = "YOUR_KEY";

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

@PostMapping("/listener")
public String listener(@RequestBody String body, @RequestHeader("Authorization-Hmac") String actual) {
try {
// Encrypt the request body with your webhook secret
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(YOUR_WEBHOOK_SECRET.getBytes(), "HmacSHA256");
mac.init(secret_key);

// Encode it to hexadecimal
byte[] hash = mac.doFinal(body.getBytes());
StringBuilder expected = new StringBuilder();
for (byte b : hash) {
expected.append(String.format("%02x", b));
}

// Compare our HMAC with your HMAC
if(!actual.equals(expected.toString())) {
System.out.println("Failed to verify!");
return "";
}
System.out.println("Successfully verified!");
} catch (Exception e) {
// Handle Exception
}
return "";
}
}

This Node.js example uses Express.js framework, if you’re unfamiliar with how Express.js framework works, we recommend reading the official documentation here.

const express = require('express');
const crypto = require('crypto')
const bodyParser = require('body-parser')
const app = express();
const port = 8080;

const YOUR_WEBHOOK_KEY = 'YOUR_SECRET'

app.use(bodyParser.json())

app.post('/listener', (req, res) => {
// Encrypt with SHA-256 and Encode to hexadecimal
let hmac = crypto.createHmac('sha256', YOUR_WEBHOOK_KEY)
.update(JSON.stringify(req.body))
.digest('hex')

// Compare our HMAC with your HMAC
if(!crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(req.get('Authorization-Hmac')))) {
console.log('Failed to verify!')
return
}
console.log('Successfully verified!')
});

app.listen(port, () => console.log(`Server is running!`));

Although you can easily compare the value of hmac variable and Authorization-Hmac header. The official documentation of Node.js recommends us to use crypto.timingSafeEqual function for extra security.

Upon receiving a Webhook message, our server created with the example code above will output either Failed to verify! or Successfully verified, the former could indicate whether there has been an error in generating the expected key or the request received is not from Tokopedia, meanwhile the latter indicates that your server has successfully verified an incoming Webhook, Congratulations!.

Have a feedback?