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()); } }