Bitbucket Cloud - Python sample code to verify webhook signature
Platform Notice: Cloud Only - This article only applies to Atlassian products on the cloud platform.
Summary
The webhook "secret" feature in Bitbucket Cloud enhances security by allowing you to validate the authenticity of webhook deliveries, ensuring they originate from Bitbucket Cloud. We have included "Hello World" examples in the webhook documentation, but slight modifications are required to test it with real-world payload examples.
In the solution section below, we provide a sample Python code that calculates and matches the signature using the secret key, payload value, and the "x-hub-signature" obtained from the headers section in the "View Requests" tab.
Solution
First, navigate to Repository Settings -> Under Workflows, click on Webhooks -> Go to the specific webhook, and under Actions, select "View Requests." You will find a list of events. Click on "View Details."
If history logging is not enabled, click on "Enable History" to load requests.
In the "View Details" section, you can observe the response, request headers, and body. Capture the X-Hub-Signature from the request headers section. Copy the payload under the body section and paste it into the "json_payload.json" file.
The below Python script performs the following tasks:
Reads the content of the "json_payload.json", strips leading and trailing whitespaces, and removes spaces after colons, and puts it in a single line.
Calculates an HMAC (Hash-based Message Authentication Code) using the SHA-256 hash function. The HMAC is computed using a secret key and the payload obtained from the JSON file.
Compares the calculated HMAC signature with a given signature (captured from the request headers section). It uses the hmac.compare_digest function for a secure comparison of the two signatures.
Prints whether the signatures match or not, along with the expected and actual signatures. This script ensures a reliable verification process for webhook signatures.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
import hashlib import hmac import json # Read the JSON payload from the file with open("json_payload.json", "r") as file: # Load JSON data from the file payload_data = json.load(file) # Convert the payload data back to a single-line JSON string modified_payload = json.dumps(payload_data, separators=(',', ':'), ensure_ascii=False) # Print the original and modified payloads print("Original Payload:") print(json.dumps(payload_data, indent=2)) # Pretty print the original payload print("\nModified Payload:") print(modified_payload) # Print the modified payload # Given HMAC signature given_signature = "sha256=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Extract the secret key secret = "" # Calculate the signature hash_object = hmac.new( secret.encode("utf-8"), msg=modified_payload.encode("utf-8"), digestmod=hashlib.sha256, ) calculated_signature = "sha256=" + hash_object.hexdigest() # Compare the signatures if given_signature is not None and not hmac.compare_digest(calculated_signature, given_signature): print( "Signatures do not match\n" f"Expected signature: {calculated_signature}\n" f"Actual signature: {given_signature}" ) else: print("Signatures match") print(f"Expected signature: {calculated_signature}") print(f"Actual signature: {given_signature}")
Explanation:
The script reads a JSON payload from the file named "json_payload.json".
It loads the JSON data into a Python dictionary called
payload_data
.The script then converts the
payload_data
dictionary back into a single-line JSON string calledmodified_payload
.It prints the original payload (
payload_data
) in a pretty-printed format and the modified payload (modified_payload
) as a single line.The script calculates an HMAC signature using the modified payload and a secret key.
It compares the calculated signature with a given signature (
given_signature
).If the signatures match, it prints a message confirming the match; otherwise, it prints the expected and actual signatures to indicate a mismatch.
Was this helpful?