Verify webhook signature with example Python code in Bitbucket Cloud

Platform Notice: Cloud Only - This article only applies to Atlassian products on the cloud platform.

Summary

This article covers how to validate Bitbucket Cloud webhook deliveries using Python code to match signatures with secret keys.

Webhook security validation

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

Trigger webhook

You need to have admin access to the repo to perform these steps.

  1. Open the repository on Bitbucket Cloud's website.

  2. Select Repository Settings from the left sidebar.

  3. Select Webhooks from the new left sidebar, under the Workflow section.

  4. Locate the specific webhook whose signature you want to verify, and select View requests under the column Actions for this webhook.

  5. By default, history is turned off for webhooks. If you see the button Enable history, select it.

  6. To see new requests on this page, you will then need to perform an action that triggers the webhook.

  7. After performing an action that triggers the webhook, select the button Load new requests to see the event.

  8. Select View details for this new event.

In the View Details section, you can observe the response, request headers, and body.

  1. Capture the X-Hub-Signature from the request headers section (you will need it later).

  2. You also need to know the webhook's secret.

  3. Copy the payload under the body section and paste it into a new file named, for example, json_payload.json.

request header
request body json

The below Python script performs the following tasks:

  1. Reads the content of the "json_payload.json", strips leading and trailing whitespaces, removes spaces after colons, and puts it in a single line.

  2. 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.

  3. 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.

  4. This script prints whether the signatures match or not, along with the expected and actual signatures. It ensures a reliable verification process for webhook signatures.

Perform these steps:

  1. Create a new file, named e.g., verify_sig.py, in the same directory as the file json_payload.json you created in Step 3 of View Details above.

  2. Copy and paste the script below into the file verify_sig.py.

  3. In line 19 of the script, replace the value of the variable given_signature with the value of the X-Hub-Signature you captured in Step 1 of View Details above.

  4. In line 22 of the script, replace the value of the variable secret with the value of the webhook's secret.

  5. Save your changes.

  6. Run the script with the command: python3 verify_sig.py.

Script verify_sig.py

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}")

Detailed script explanation:

  1. The script reads a JSON payload from the file named "json_payload.json".

  2. It loads the JSON data into a Python dictionary called payload_data.

  3. The script then converts the payload_data dictionary back into a single-line JSON string called modified_payload.

  4. It prints the original payload (payload_data) in a pretty-printed format and the modified payload (modified_payload) as a single line.

  5. The script calculates an HMAC signature using the modified payload and a secret key.

  6. It compares the calculated signature with a given signature (given_signature).

  7. If the signatures match, it prints a message confirming the match; otherwise, it prints the expected and actual signatures to indicate a mismatch.

Updated on June 11, 2025

Still need help?

The Atlassian Community is here for you.