Third-party applications can obtain a store's review data from Trustoo after being authorized by the store. By subscribing to webhooks, you can receive updates on review data.
INFO
⚠️ Important Update (November 2025)
We have upgraded our signature algorithm from SHA-256 with salt to HMAC-SHA256 for enhanced security and better cross-platform compatibility.
Key Changes:
Migration Required: All existing integrations must update their signature generation code. See section 2.2 for updated implementation examples in all languages.
After the merchant is required to provide Trustoo's public token and private token, it can obtain authorization from the merchant's Trustoo system. In the following text, Trustoo's public token and private token are all referred to as public token and private token.
The public token is the unique identifier of the store in the third-party application.
The private token of the user is a crucial key used to authorize third-party applications to access review information. Please keep it confidential and only provide it to trusted developers. In case of any leakage, please contact us promptly for a reset.
CAUTION
The original private token becomes immediately invalid after a reset. Please contact all authorized third-party applications to update the private token.
TIP
Merchants can view the public token and private token in the Trustoo backend of their store.
The API address for our production environment is https://rapi.trustoo.io/
| HEADER NAME | DESCRIPTION | EXAMPLE |
|---|---|---|
| public-token | It is the unique identifier of the store in the third-party application. | GfKu22TCaj4gULtrA3/OgA== |
| timestamp | This is a Unix timestamp. | 1700465771 |
| sign | It is the unique identifier of the store in the third-party application. | c3f6910a4dc12969c819f47621e5adf608e2a33f4db0575988bd4fc19173a582 |
Post Reviews API Doc
This is the detailed content
# Note: Generate a fresh timestamp and signature for each request
# The signature shown below is just an example - you must generate your own
curl --location --request POST 'https://rapi.trustoo.io/api/v1/openapi/create_shop_webhook' \
--header 'Public-token: GfKu22TCaj4gULtrA3/OgA==' \
--header 'sign: [your_generated_signature_here]' \
--header 'timestamp: [current_unix_timestamp]' \
--header 'Content-Type: application/json' \
--data-raw '{
"topic":"review/created",
"url":"https://your-webhook-url.com"
}'
# To generate the signature:
# 1. Build string: timestamp=[timestamp]|{"topic":"review/created","url":"https://your-webhook-url.com"}
# 2. Calculate: HMAC-SHA256(string, private_token)
# 3. Convert to lowercase hex format
Example with actual values:
# Given:
# - timestamp: 1732180800
# - private_token: your_private_token
# - body: {"topic":"review/created","url":"https://your-webhook-url.com"}
# Signature string: timestamp=1732180800|{"topic":"review/created","url":"https://your-webhook-url.com"}
# Generated signature: ee5d60dd3fbc3c70e49405564f4830466a11382f088eb127c5c5237c5c9b3e91
curl --location --request POST 'https://rapi.trustoo.io/api/v1/openapi/create_shop_webhook' \
--header 'Public-token: GfKu22TCaj4gULtrA3/OgA==' \
--header 'sign: ee5d60dd3fbc3c70e49405564f4830466a11382f088eb127c5c5237c5c9b3e91' \
--header 'timestamp: 1732180800' \
--header 'Content-Type: application/json' \
--data-raw '{
"topic":"review/created",
"url":"https://your-webhook-url.com"
}'
We use HMAC-SHA256 (Hash-based Message Authentication Code) for signature generation, which is the same secure approach used by major platforms like Shopify and Stripe.
Signature Generation Steps:
key1=value1&key2=value2| separatorKey Advantages:
CAUTION
The timestamp is only valid within 15 minutes. It needs to be regenerated if it exceeds this timeframe.
import (
"crypto/hmac"
"crypto/sha256"
"fmt"
"sort"
"strconv"
"strings"
"time"
)
func TestSignData(t *testing.T) {
data := map[string]string{
"banana": "yellow",
"apple": "red",
"grape": "purple",
"orange": "orange",
"timestamp": strconv.Itoa(int(time.Now().UTC().Unix())),
}
sign := SignData(data, "123")
fmt.Println("=========sign=======:", sign)
}
// SignData signs the data using HMAC-SHA256
func SignData(data map[string]string, privateToken string) string {
// 1. Retrieve and sort the keys
var keys []string
for key := range data {
keys = append(keys, key)
}
sort.Strings(keys)
// 2. Traverse the sorted keys and concatenate them into a string
var resultStr string
for _, key := range keys {
value := data[key]
resultStr += fmt.Sprintf("%s=%s&", key, value)
}
// Remove the trailing "&"
resultStr = strings.TrimSuffix(resultStr, "&")
// 3. Generate HMAC-SHA256 signature
h := hmac.New(sha256.New, []byte(privateToken))
h.Write([]byte(resultStr))
return fmt.Sprintf("%x", h.Sum(nil))
}
// For POST requests with JSON body, append body after "|" separator
func SignDataWithBody(data map[string]string, body string, privateToken string) string {
// 1. Retrieve and sort the keys
var keys []string
for key := range data {
keys = append(keys, key)
}
sort.Strings(keys)
// 2. Build signature string
var resultStr string
for _, key := range keys {
value := data[key]
resultStr += fmt.Sprintf("%s=%s&", key, value)
}
resultStr = strings.TrimSuffix(resultStr, "&")
// 3. Append body if present
if body != "" {
resultStr = resultStr + "|" + body
}
// 4. Generate HMAC-SHA256 signature
h := hmac.New(sha256.New, []byte(privateToken))
h.Write([]byte(resultStr))
return fmt.Sprintf("%x", h.Sum(nil))
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;
public class Signer {
/**
* Sign data using HMAC-SHA256
* @param data Parameters to sign
* @param privateToken Private token (secret key)
* @return Signature in hexadecimal format
*/
public static String signData(Map<String, String> data, String privateToken) {
try {
// 1. Retrieve and sort the keys
TreeMap<String, String> sortedData = new TreeMap<>(data);
// 2. Traverse the sorted keys and concatenate them into a string
StringBuilder resultStr = new StringBuilder();
for (Map.Entry<String, String> entry : sortedData.entrySet()) {
resultStr.append(String.format("%s=%s&", entry.getKey(), entry.getValue()));
}
// Remove the trailing "&"
resultStr.deleteCharAt(resultStr.length() - 1);
// 3. Generate HMAC-SHA256 signature
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
privateToken.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(secretKey);
byte[] hashed = mac.doFinal(resultStr.toString().getBytes(StandardCharsets.UTF_8));
// Convert to hexadecimal string
StringBuilder hexString = new StringBuilder();
for (byte b : hashed) {
hexString.append(String.format("%02x", b));
}
return hexString.toString();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to generate signature", e);
}
}
/**
* Sign data with request body (for POST requests)
* @param data Parameters to sign
* @param body Request body JSON string
* @param privateToken Private token (secret key)
* @return Signature in hexadecimal format
*/
public static String signDataWithBody(Map<String, String> data, String body, String privateToken) {
try {
// 1. Retrieve and sort the keys
TreeMap<String, String> sortedData = new TreeMap<>(data);
// 2. Build signature string
StringBuilder resultStr = new StringBuilder();
for (Map.Entry<String, String> entry : sortedData.entrySet()) {
resultStr.append(String.format("%s=%s&", entry.getKey(), entry.getValue()));
}
resultStr.deleteCharAt(resultStr.length() - 1);
// 3. Append body if present
if (body != null && !body.isEmpty()) {
resultStr.append("|").append(body);
}
// 4. Generate HMAC-SHA256 signature
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
privateToken.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(secretKey);
byte[] hashed = mac.doFinal(resultStr.toString().getBytes(StandardCharsets.UTF_8));
// Convert to hexadecimal string
StringBuilder hexString = new StringBuilder();
for (byte b : hashed) {
hexString.append(String.format("%02x", b));
}
return hexString.toString();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to generate signature", e);
}
}
public static void main(String[] args) {
// Example usage
Map<String, String> data = new TreeMap<>();
data.put("key1", "value1");
data.put("key2", "value2");
data.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
String privateToken = "your_private_token";
String signature = signData(data, privateToken);
System.out.println("Signature: " + signature);
}
}
<?php
/**
* Sign data using HMAC-SHA256
* @param array $data Parameters to sign
* @param string $privateToken Private token (secret key)
* @return string Signature in hexadecimal format
*/
function signData($data, $privateToken) {
// Retrieve and sort the keys
$keys = array_keys($data);
sort($keys);
// Traverse the sorted keys and concatenate them into a string
$resultStr = '';
foreach ($keys as $key) {
$value = $data[$key];
$resultStr .= "$key=$value&";
}
// Remove the trailing "&"
$resultStr = rtrim($resultStr, '&');
// Generate HMAC-SHA256 signature
$signature = hash_hmac('sha256', $resultStr, $privateToken);
return $signature;
}
/**
* Sign data with request body (for POST requests)
* @param array $data Parameters to sign
* @param string $body Request body JSON string
* @param string $privateToken Private token (secret key)
* @return string Signature in hexadecimal format
*/
function signDataWithBody($data, $body, $privateToken) {
// Retrieve and sort the keys
$keys = array_keys($data);
sort($keys);
// Build signature string
$resultStr = '';
foreach ($keys as $key) {
$value = $data[$key];
$resultStr .= "$key=$value&";
}
$resultStr = rtrim($resultStr, '&');
// Append body if present
if ($body !== '' && $body !== null) {
$resultStr .= '|' . $body;
}
// Generate HMAC-SHA256 signature
$signature = hash_hmac('sha256', $resultStr, $privateToken);
return $signature;
}
// Example usage for GET request
$data = array(
'param1' => 'value1',
'param2' => 'value2',
'timestamp' => time(),
// Add other parameters...
);
$privateToken = 'your_private_token';
$signature = signData($data, $privateToken);
echo "Signature: " . $signature . "\n";
// Example usage for POST request with body
$postData = array(
'timestamp' => time()
);
$bodyJson = json_encode(array(
'topic' => 'review/created',
'url' => 'https://your-webhook-url.com'
));
$postSignature = signDataWithBody($postData, $bodyJson, $privateToken);
echo "POST Signature: " . $postSignature . "\n";
?>
const crypto = require('crypto');
/**
* Sign data using HMAC-SHA256
* @param {Object} data - Parameters to sign
* @param {string} privateToken - Private token (secret key)
* @returns {string} Signature in hexadecimal format
*/
function signData(data, privateToken) {
// Retrieve and sort the keys
const keys = Object.keys(data).sort();
// Traverse the sorted keys and concatenate them into a string
let resultStr = '';
for (const key of keys) {
const value = data[key];
resultStr += `${key}=${value}&`;
}
// Remove the trailing "&"
resultStr = resultStr.slice(0, -1);
// Generate HMAC-SHA256 signature
const signature = crypto
.createHmac('sha256', privateToken)
.update(resultStr, 'utf8')
.digest('hex');
return signature;
}
/**
* Sign data with request body (for POST requests)
* @param {Object} data - Parameters to sign
* @param {string} body - Request body JSON string
* @param {string} privateToken - Private token (secret key)
* @returns {string} Signature in hexadecimal format
*/
function signDataWithBody(data, body, privateToken) {
// Retrieve and sort the keys
const keys = Object.keys(data).sort();
// Build signature string
let resultStr = '';
for (const key of keys) {
const value = data[key];
resultStr += `${key}=${value}&`;
}
resultStr = resultStr.slice(0, -1);
// Append body if present
if (body) {
resultStr += '|' + body;
}
// Generate HMAC-SHA256 signature
const signature = crypto
.createHmac('sha256', privateToken)
.update(resultStr, 'utf8')
.digest('hex');
return signature;
}
// Example usage for GET request
const data = {
"param1": 'value1',
"param2": 'value2',
"timestamp": Math.floor(Date.now() / 1000).toString(),
// Add other parameters...
};
const privateToken = 'your_private_token';
const signature = signData(data, privateToken);
console.log('Signature:', signature);
// Example usage for POST request with body
const postData = {
"timestamp": Math.floor(Date.now() / 1000).toString()
};
const bodyJson = JSON.stringify({
"topic": 'review/created',
"url": 'https://your-webhook-url.com'
});
const postSignature = signDataWithBody(postData, bodyJson, privateToken);
console.log('POST Signature:', postSignature);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
// Example usage for GET request
Dictionary<string, string> data = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
{ "timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString() },
// Add other parameters...
};
string privateToken = "your_private_token";
string signature = SignData(data, privateToken);
Console.WriteLine("Signature: " + signature);
// Example usage for POST request with body
Dictionary<string, string> postData = new Dictionary<string, string>
{
{ "timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString() }
};
string bodyJson = "{\"topic\":\"review/created\",\"url\":\"https://your-webhook-url.com\"}";
string postSignature = SignDataWithBody(postData, bodyJson, privateToken);
Console.WriteLine("POST Signature: " + postSignature);
}
/// <summary>
/// Sign data using HMAC-SHA256
/// </summary>
/// <param name="data">Parameters to sign</param>
/// <param name="privateToken">Private token (secret key)</param>
/// <returns>Signature in hexadecimal format</returns>
static string SignData(Dictionary<string, string> data, string privateToken)
{
// Retrieve and sort the keys
var keys = data.Keys.ToList();
keys.Sort();
// Traverse the sorted keys and concatenate them into a string
StringBuilder resultStr = new StringBuilder();
foreach (var key in keys)
{
string value = data[key];
resultStr.AppendFormat("{0}={1}&", key, value);
}
// Remove the trailing "&"
resultStr.Length--;
// Generate HMAC-SHA256 signature
using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(privateToken)))
{
byte[] hashedBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(resultStr.ToString()));
return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower();
}
}
/// <summary>
/// Sign data with request body (for POST requests)
/// </summary>
/// <param name="data">Parameters to sign</param>
/// <param name="body">Request body JSON string</param>
/// <param name="privateToken">Private token (secret key)</param>
/// <returns>Signature in hexadecimal format</returns>
static string SignDataWithBody(Dictionary<string, string> data, string body, string privateToken)
{
// Retrieve and sort the keys
var keys = data.Keys.ToList();
keys.Sort();
// Build signature string
StringBuilder resultStr = new StringBuilder();
foreach (var key in keys)
{
string value = data[key];
resultStr.AppendFormat("{0}={1}&", key, value);
}
resultStr.Length--;
// Append body if present
if (!string.IsNullOrEmpty(body))
{
resultStr.Append("|");
resultStr.Append(body);
}
// Generate HMAC-SHA256 signature
using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(privateToken)))
{
byte[] hashedBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(resultStr.ToString()));
return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower();
}
}
}
Endpoint: GET /api/v1/openapi/get_reviews
Query Parameters:
product_ids=9228589138176
page_size=20
page=1
Step 1: Add timestamp
product_ids=9228589138176
page_size=20
page=1
timestamp=1732180800
Step 2: Sort alphabetically
page=1
page_size=20
product_ids=9228589138176
timestamp=1732180800
Step 3: Build signature string
page=1&page_size=20&product_ids=9228589138176×tamp=1732180800
Step 4: Generate HMAC-SHA256
HMAC-SHA256(signature_string, private_token)
Result: c3648110bc3f1f63e7ed54fc4390448bfd2a131b405aeed91cd26a42b3ee9a93
Endpoint: POST /api/v1/openapi/create_shop_webhook
Request Body:
{
"topic": "review/created",
"url": "https://your-webhook-url.com"
}
Step 1: Query parameters (only timestamp for POST)
timestamp=1732180800
Step 2: Build signature string (params + | + body)
timestamp=1732180800|{"topic":"review/created","url":"https://your-webhook-url.com"}
Step 3: Generate HMAC-SHA256
HMAC-SHA256(signature_string, private_token)
Result: ee5d60dd3fbc3c70e49405564f4830466a11382f088eb127c5c5237c5c9b3e91
Important Notes:
| separator is used between parameters and body