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