Webhook signatures at Trustpair
Webhooks sent by Trustpair are signed. Verifying webhooks' signature helps ensure the authenticity and integrity of webhook payloads sent by Trustpair. A valid signature means that the message actually comes from Trustpair and that the content has not been tampered with or altered in transit.
Trustpair uses the SHA-256 to hash the payload that is sent to your application via the webhooks. Also, Trustpair sends the following information in the signature header of each webhook request:
- The request timestamp:
x-trustpair-timestamp
- The request signature:
x-trustpair-signature
Signature verification process
- Start by concatenating the request timestamp with the payload from the webhooks answer like this
{x-trustpair-timestamp}:{json_payload_answer}
- Using the SHA 256 hashing function, produce the hash of the above concatenation with the API token of your environment as a Secret key. If you want to make a quick test with no code, you can use the following online tool to generate your hash.
- Finally, you need to verify that the hashed output matches the value of the request signature.
Code examples
Here are examples of code in various programming languages you can use out-of-the-box to implement the webhook signature verification inside your code
const crypto = require('crypto');
const api_token = "57s4h6cxduCs9GO9";
const request = {
headers: {
"X-Trustpair-Signature": "sha256=58de2260d2be3a130834a329ac684d7c895cc276b8a76fbb2b5d5e0597b4b854",
"X-Trustpair-Timestamp": "1691760849"
},
body: "raw-request-body"
};
class TrustpairSignatureCheckService {
constructor(api_token, request) {
this.api_token = api_token;
this.request = request;
}
valid_signature() {
return this.computed_signature() === this.request_signature();
}
computed_signature() {
const signature_data = `${this.request_timestamp()}:${this.request_body()}`;
const digest = crypto.createHmac('sha256', this.api_token).update(signature_data).digest('hex');
return `sha256=${digest}`;
}
request_timestamp() {
return this.request.headers["X-Trustpair-Timestamp"];
}
request_body() {
return this.request.body;
}
request_signature() {
return this.request.headers["X-Trustpair-Signature"];
}
}
const trustpairService = new TrustpairSignatureCheckService(api_token, request);
console.log(trustpairService.valid_signature());
api_token = "57s4h6cxduCs9GO9"
request = OpenStruct.new({
"headers" => {
"X-Trustpair-Signature" => "sha256=58de2260d2be3a130834a329ac684d7c895cc276b8a76fbb2b5d5e0597b4b854",
"X-Trustpair-Timestamp" => "1691760849"
},
"body" => "raw-request-body"
})
class TrustpairSignatureCheckService
attr_reader :api_token, :request
def initialize(api_token, request)
@api_token = api_token
@request = request
end
def valid_signature?
computed_signature == request_signature
end
private
def computed_signature
signature_data = "#{request_timestamp}:#{request_body}"
digest = OpenSSL::HMAC.hexdigest('SHA256', api_token, signature_data)
"sha256=#{digest}"
end
def request_timestamp
request.headers["X-Trustpair-Timestamp"]
end
def request_body
request.body
end
def request_signature
request.headers["X-Trustpair-Signature"]
end
end
TrustpairSignatureCheckService.new(api_token, request).valid_signature?
import hashlib
from collections import namedtuple
class OpenStruct:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
api_token = "57s4h6cxduCs9GO9"
request = OpenStruct(
headers = {
"X-Trustpair-Signature": "sha256=58de2260d2be3a130834a329ac684d7c895cc276b8a76fbb2b5d5e0597b4b854",
"X-Trustpair-Timestamp": "1691760849"
},
body = "raw-request-body"
)
class TrustpairSignatureCheckService:
def __init__(self, api_token, request):
self.api_token = api_token
self.request = request
def valid_signature(self):
return self.computed_signature() == self.request_signature()
def computed_signature(self):
signature_data = f"{self.request_timestamp()}:{self.request_body()}"
digest = hashlib.sha256(self.api_token.encode() + signature_data.encode()).hexdigest()
return f"sha256={digest}"
def request_timestamp(self):
return self.request.headers["X-Trustpair-Timestamp"]
def request_body(self):
return self.request.body
def request_signature(self):
return self.request.headers["X-Trustpair-Signature"]
trustpair_service = TrustpairSignatureCheckService(api_token, request)
print(trustpair_service.valid_signature())
class OpenStruct {
private $data = array();
public function __construct($data) {
$this->data = $data;
}
public function __get($name) {
return $this->data[$name];
}
}
$api_token = "57s4h6cxduCs9GO9";
$request = new OpenStruct(array(
"headers" => array(
"X-Trustpair-Signature" => "sha256=58de2260d2be3a130834a329ac684d7c895cc276b8a76fbb2b5d5e0597b4b854",
"X-Trustpair-Timestamp" => "1691760849"
),
"body" => "raw-request-body"
));
class TrustpairSignatureCheckService {
private $api_token;
private $request;
public function __construct($api_token, $request) {
$this->api_token = $api_token;
$this->request = $request;
}
public function valid_signature() {
return $this->computed_signature() === $this->request_signature();
}
private function computed_signature() {
$signature_data = $this->request_timestamp() . ":" . $this->request_body();
$digest = hash_hmac('sha256', $signature_data, $this->api_token);
return "sha256=" . $digest;
}
private function request_timestamp() {
return $this->request->headers["X-Trustpair-Timestamp"];
}
private function request_body() {
return $this->request->body;
}
private function request_signature() {
return $this->request->headers["X-Trustpair-Signature"];
}
}
$trustpair_service = new TrustpairSignatureCheckService($api_token, $request);
echo $trustpair_service->valid_signature() ? "true" : "false";
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
type OpenStruct struct {
data map[string]interface{}
}
func NewOpenStruct(data map[string]interface{}) *OpenStruct {
return &OpenStruct{data: data}
}
func (o *OpenStruct) Get(key string) interface{} {
return o.data[key]
}
func main() {
apiToken := "57s4h6cxduCs9GO9"
request := NewOpenStruct(map[string]interface{}{
"headers": map[string]string{
"X-Trustpair-Signature": "sha256=58de2260d2be3a130834a329ac684d7c895cc276b8a76fbb2b5d5e0597b4b854",
"X-Trustpair-Timestamp": "1691760849",
},
"body": "raw-request-body",
})
type TrustpairSignatureCheckService struct {
apiToken string
request *OpenStruct
}
func NewTrustpairSignatureCheckService(apiToken string, request *OpenStruct) *TrustpairSignatureCheckService {
return &TrustpairSignatureCheckService{
apiToken: apiToken,
request: request,
}
}
func (t *TrustpairSignatureCheckService) ValidSignature() bool {
return t.computedSignature() == t.requestSignature()
}
func (t *TrustpairSignatureCheckService) computedSignature() string {
signatureData := fmt.Sprintf("%s:%s", t.requestTimestamp(), t.requestBody())
h := hmac.New(sha256.New, []byte(t.apiToken))
h.Write([]byte(signatureData))
digest := hex.EncodeToString(h.Sum(nil))
return fmt.Sprintf("sha256=%s", digest)
}
func (t *TrustpairSignatureCheckService) requestTimestamp() string {
return t.request.Get("headers").(map[string]string)["X-Trustpair-Timestamp"]
}
func (t *TrustpairSignatureCheckService) requestBody() string {
return t.request.Get("body").
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Map;
import java.util.HashMap;
class OpenStruct {
private Map<String, Object> data = new HashMap<>();
public OpenStruct(Map<String, Object> data) {
this.data = data;
}
public Object get(String key) {
return data.get(key);
}
}
public class TrustpairSignatureCheckService {
private String apiToken;
private OpenStruct request;
public TrustpairSignatureCheckService(String apiToken, OpenStruct request) {
this.apiToken = apiToken;
this.request = request;
}
public boolean validSignature() {
return computedSignature().equals(requestSignature());
}
private String computedSignature() throws Exception {
String signatureData = requestTimestamp() + ":" + requestBody();
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(apiToken.getBytes(), "HmacSHA256");
hmacSha256.init(secretKey);
byte[] digest = hmacSha256.doFinal(signatureData.getBytes());
return "sha256=" + bytesToHex(digest);
}
private String requestTimestamp() {
return (String) request.get("headers").get("X-Trustpair-Timestamp");
}
private String requestBody() {
return (String) request.get("body");
}
private String requestSignature() {
return (String) request.get("headers").get("X-Trustpair-Signature");
}
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return result.toString();
}
public static void main(String[] args) {
String apiToken = "57s4h6cxduCs9GO9";
Map<String, Object> headers = new HashMap<>();
headers.put("X-Trustpair-Signature", "sha256=58de2260d2be3a130834a329ac684d7c895cc276b8a76fbb2b5d5e0597b4b854");
headers.put("X-Trustpair-Timestamp", "1691760849");
Map<String, Object> requestData = new HashMap<>();
requestData.put("headers", headers);
requestData.put("body", "raw-request-body");
OpenStruct request = new OpenStruct(requestData);
TrustpairSignatureCheckService trustpairService = new TrustpairSignatureCheckService(apiToken, request);
System.out.println(trustpairService.validSignature());
}
}