Check the webhook signatures

Overview #

Verify the events that Wooshpay sends to your webhook endpoints.

Wooshpay signature header #

The Wooshpay-Signature header included in each signed event contains a timestamp and signature.

The timestamp is prefixed by t=, and each signature is prefixed by v1.

For example:

Wooshpay-Signature:t=1492774577,v1=6fdfb9c357542b8ee07277f5fca2c6f728bae2dce9be2f91412f4de922c1bae4

Webhook secret key #

Wooshpay generates a unique secret key for each webhook. whsec_

Before verify signatures, you need to retrieve your webhook secret from webhook.secret in webhook object.

webhook-secret-key

Verifying signatures #

By comparing the the signature in the header to the expected signature, You can verify that the even were sent by Wooshpay, not by a third party.

To verify the signature step by step, you can follow the guide below:

Step 1: Extract the timestamp and signatures from the header #

Split the header, using the , character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a prefix and value pair.

The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature. You can discard all other elements.

For example:

t=1687845304,v1=6fdfb9c357542b8ee07277f5fca2c6f728bae2dce9be2f91412f4de922c1bae4

Step 2: Prepare the signed_payload string #

The signed_payload string is created by concatenating:

  • The timestamp (as a string)
  • The character .
  • The actual JSON payload (the request body)

For example:

1687845304+.+JSON payload

1687845304.{
  "id": "evt_1NNUrjL6kclEVx6Mb1x5dKJ3",
  "object": "event",
  "api_version": "2022-11-15",
  "created": 1687845303,
  "data": {
    "object": {
      "id": "prod_O9oUVgsSaordCT",
      "object": "product",
      "active": true,
      "livemode": true,
      "name": "test",
      "type": "service",
  "livemode": true,
  "pending_webhooks": 1,
  "type": "product.created"
}

Step 3: Determine the expected signature #

Compute a HMAC with the SHA256 hash function.

  • Use the endpoint’s webhook secret as the key, which you can get it from webhook.secret in webhook object
  • Use the signed_payload string as the message which you already prepared in step 2.

Through the key + message, you can compute a HMAC string, which is expected signature

For example:

 public static void main(String[] args) {
        String webhookSecret = "whsec_261V2mfsXt1BsOjJbHaQOxnTzhWZKrUE";
        String timestamp = "1687845304";
        String requestBody = "{\"id\":\"evt_1NNUrjL6kclEVx6Mb1x5dKJ3\",\"object\":\"event\",\"api_version\":\"2022-11-15\",\"created\":1687845303,\"data\":{\"object\":{\"id\":\"prod_O9oUVgsSaordCT\",\"object\":\"product\",\"active\":true,\"livemode\":true,\"name\":\"test\",\"type\":\"service\",\"livemode\":true,\"pending_webhooks\":1,\"type\":\"product.created\"}";
        String signedPayload = timestamp+"."+requestBody;
        String signature = hmacSha256(webhookSecret,signedPayload);
        String WooshpaySignature = "t="+timestamp+",v1="+signature;
    }

    /**
     * HMAC-SHA256 
     */
    public static String hmacSha256(String secret, String message) {
        String res;
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKey secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
            mac.init(secretKey);
            byte[] hash = mac.doFinal(message.getBytes());
            res = Hex.encodeHexString(hash);
        } catch (Exception e) {
            return null;
        }
        return res;
    }

Step 4: Compare the signatures #

Compare the signature in the header to the expected signature.

For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.

To protect against timing attacks, you can use a constant-time string comparison to compare the expected signature to each of the received signatures.

What are your feelings
Updated on August 5, 2023