With webhooks, you receive programmatic notifications when changes happen in Blue Canvas. You can register a webhook on the settings page.

Creating a Webhook Subscription

  1. From Blue Canvas, go to Settings > Webhooks.
  2. Click on New Webhook.
  3. Enter the URL of your event listener, and click Save.
  4. You can reveal the Signing Secret to use in the next step.

Verifying authenticity

When your API gets requested by Blue Canvas, we always include the X-Bluecanvas-Signature-HS256 header. This header contains a HMAC (Hash-Based Message Authentication Code) signature using SHA-256 of the request.

Encoding is done in the following way:

time_safe_compare(valueInHeader, base64_encode(sha256_hmac(SIGNING_SECRET, REQUEST_BODY)))

public class BlueCanvasSignatureVerifier {
    public static Boolean verify(String webhookSecret, String requestBody, String requestSignatureFromHeader) {
        Blob privateKey = Blob.valueOf(webhookSecret);
        Blob data = Blob.valueOf(requestBody);
        Blob macToVerify = EncodingUtil.base64Decode(requestSignatureFromHeader);
        
        return Crypto.verifyHMac('hmacSHA256', data, privateKey, macToVerify);
    }
    
    @IsTest
    static void testSign() {
        // TODO Move to test class for coverage
        System.assertEquals(
            'yHe0ALeSA8vdSagOvh6bNCtOQCBY9R6tr5xQfJH69ng=',
        	BlueCanvasSignatureVerifier.sign(
                'ExampleSecretJustForTesting',
                '{"example": "Please do not alter the JSON formatting, the body should be used as-is"}'
            )
        );
    }       
}
import hmac
import hashlib
from base64 import b64decode


def verify_hash(
    webhook_secret: str, request_body: str, request_signature_from_header: str
) -> bool:
    private_key = webhook_secret.encode()
    data = request_body.encode()
    digest = hmac.new(private_key, data, hashlib.sha256).digest()
    expected_digest = b64decode(request_signature_from_header)

    return hmac.compare_digest(digest, expected_digest)


# TODO Remove the line below
print(
    verify_hash(
        "ExampleSecretJustForTesting",
        '{"example": "Please do not alter the JSON formatting, the body should be used as-is"}',
        "yHe0ALeSA8vdSagOvh6bNCtOQCBY9R6tr5xQfJH69ng=",
    )
)

// You can also use verifyHMac from our SDK
export function verifyHMac(
  webhookSecret: string,
  requestBody: Buffer | string,
  requestSignatureFromHeader: string
): boolean {
  const hmac = createHmac("sha256", webhookSecret).update(requestBody);
  const digest = hmac.digest();
  const expectedDigest = Buffer.from(requestSignatureFromHeader, "base64");
  if (digest.length !== expectedDigest.length) {
    return false;
  }

  return timingSafeEqual(digest, expectedDigest);
}

describe("Utils", () => {
  it("Can verify a webhook signature", () => {
    const result = verifyHMac(
      "ExampleSecretJustForTesting",
      '{"example": "Please do not alter the JSON formatting, the body should be used as-is"}',
      "yHe0ALeSA8vdSagOvh6bNCtOQCBY9R6tr5xQfJH69ng="
    );

    expect(result).toBe(true);
  });
});

You can test with the following data (please note that none of them end with a new-line character):

ExampleSecretJustForTesting
{"example": "Please do not alter the JSON formatting, the body should be used as-is"}
yHe0ALeSA8vdSagOvh6bNCtOQCBY9R6tr5xQfJH69ng=

More about the used technologies

Security consideration

Please make sure you use HTTPS in order to prevent replay attacks or make sure you do not execute duplicate actions.

Tips and tricks

If you like inspect the webhooks before starting your integration we recommend using webhook.site note that we are not affiliated with this app.