Bitbucket webhooks: event keys, headers, secret and request history

To test a Bitbucket Cloud webhook, set a free webhooks.sh bin URL as the webhook URL and push a commit: the repo:push payload and its headers show up live in the browser. No signup; a bin lasts 48 hours and takes 100 requests a day.

A Bitbucket webhook is an HTTP request that Bitbucket Cloud sends to your URL whenever an event happens in a repository, such as a push or a pull request. The fastest way to inspect one is to paste a free bin URL into your repository settings and push a commit. There's no signup, and the bin stays up for 48 hours and takes 100 requests a day. Below, we cover setting up the webhook, reading its headers and event keys, verifying the secret signature, checking request history and retries, and reading a push payload.

Point a Bitbucket webhook at a free bin

  1. Open view.webhooks.page in your browser to create a free bin, or run curl -X POST https://webhooks.sh/v1/bins in your terminal.
  2. In your Bitbucket repository (you need repository administrator access for this), go to More actions > Settings > Webhooks > Add webhook, then give it a Title and paste your bin's ingest_url. Since a bin accepts any path under its URL, you can append paths like /bitbucket/push and /bitbucket/pr to keep separate webhooks easy to tell apart.
  3. Under Triggers, leave Repository push selected or pick specific events from the full list, optionally enter or generate a Secret, and click Save.
  4. Push a commit or open a pull request in the repo. The delivery shows up live in your bin viewer with its headers and JSON body, and if you turned on request history in Bitbucket, it logs an HTTP 200 status.
# Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins

# Use the ingest_url as the webhook URL, one path per webhook:
#   https://<bin>.webhooks.page/bitbucket/push
#   https://<bin>.webhooks.page/bitbucket/pr
# Push a commit, then read the capture:
curl -H "x-view-key: <view key>" \
  https://view.webhooks.page/api/bins/<bin>/deliveries

Headers and event keys

Bitbucket sets X-Event-Key on every delivery to tell you what happened, such as repo:push or pullrequest:created (note that pullrequest:fulfilled means a PR was merged, and pullrequest:rejected means it was declined). One catch with our viewer: a free bin automatically redacts any header with key in the name, so X-Event-Key shows up as redacted even though it's not a secret.

You can work around this in two ways: point separate webhooks at distinct paths like /bitbucket/push, or inspect the JSON body directly, since push deliveries include a top-level push object, pull request events carry a pullrequest object, and issue events have an issue object. The other Bitbucket headers (X-Hub-Signature, X-Hook-UUID, X-Request-UUID and X-Attempt-Number) stay visible in the viewer.

HeaderWhat it holdsIn a free bin
X-Event-KeyEvent, for example repo:pushRedacted (name contains key)
X-Hub-Signaturesha256= plus a hex HMAC, only with a secretVisible
X-Hook-UUIDID of the webhookVisible
X-Request-UUIDID of this requestVisible
X-Attempt-NumberAttempt number, 1 to 3Visible

Check the X-Hub-Signature from your secret

When you set a Bitbucket webhook secret on creation, copy it somewhere safe before clicking Save, because Bitbucket won't show it to you again. Once a secret is configured, every delivery includes an X-Hub-Signature header formatted as sha256= followed by the hex HMAC-SHA256 digest of the raw request body, keyed with your secret.

Make sure your receiver hashes the raw bytes exactly as they arrived over the wire, before parsing the JSON, and compares the digests in constant time. The snippet below uses Bitbucket's published test vector (the secret It's a Secret to Everybody and payload Hello World!), which prints True. Because X-Hub-Signature isn't redacted in a bin, you can also copy a real payload and signature from your browser into our HMAC signature verifier to check them by hand.

import hashlib, hmac

def verify(body: bytes, header: str, secret: str) -> bool:
    method, _, sig = header.partition("=")
    mac = hmac.new(secret.encode(), body, hashlib.sha256)
    return method == "sha256" and hmac.compare_digest(
        mac.hexdigest(), sig)

# Bitbucket's published test vector, prints True
print(verify(b"Hello World!",
             "sha256=a4771c39fbe90f317c7824e83ddef3caae9cb3d9"
             "76c214ace1f2937e133263c9",
             "It's a Secret to Everybody"))

Request history, timeout and retries

To see Bitbucket's delivery logs, go to Repository settings > Webhooks > View requests and click Enable history. Each entry logs the event, timestamp, status (TIMEOUT, NETWORK ERROR, or the HTTP status code), and request details. Bitbucket waits up to 10 seconds for your server to respond, and if it gets a 500 to 599 status code, it retries the delivery up to two more times (for a maximum of 3 tries, tracked in X-Attempt-Number). A webhooks.sh bin answers 200 right away, so Bitbucket doesn't retry.

There are two size and rate limits to watch out for when running a Bitbucket webhook test:

  • Bitbucket caps webhook payloads at 256 KB, and anything larger can silently fail to trigger.
  • Free bins reject bodies over 100KB with 413 (body_too_large) and return 429 (daily_cap_reached) after 100 requests in a day, both of which show up in Bitbucket's history without being retried.

If you need the bin past 48 hours, claim it with a free Google sign-in to keep the same URL and a rolling 30 days of history at 100 requests a day. Upgrading to Solo ($12/month) gives you persistent endpoints on webhooks.host, 250,000 requests per billing period, bodies up to 5MB, and local relaying with wsh listen (note that Solo's built-in signature verification covers Stripe, GitHub, and Shopify, not Bitbucket).

LimitValue
Webhooks per repository50
Response timeout10 seconds
Attempts after a 500 to 599 response3 (first try plus 2 resends)
Maximum payload256 KB
Commits listed per change in repo:push5 most recent

What a repo:push payload looks like

Every repo:push JSON body has three top-level fields: actor (the user who pushed), repository, and push, whose changes array holds one entry for each branch or tag that moved. Inside each change object, you'll find new and old (pointing to the ref and its target commit), boolean flags for created, closed, and forced, a commits array holding up to the 5 most recent commits, and a truncated boolean that flips to true when the push included more than 5 commits.

Other event types swap out the push key: pull request payloads contain actor, pullrequest, and repository, while issue payloads have actor, issue, and repository. Below is Bitbucket's documented push payload example with the nested objects emptied out so you can see the structure.

{
  "actor": {},
  "repository": {},
  "push": {
    "changes": [
      {
        "new": {
          "type": "branch",
          "name": "name-of-branch",
          "target": {
            "type": "commit",
            "hash": "709d658dc5b6d6afcd46049c2f332ee3f515a67d",
            "message": "new commit message\n",
            "date": "2015-06-09T03:34:49+00:00"
          }
        },
        "old": {},
        "created": false,
        "forced": false,
        "closed": false,
        "commits": [],
        "truncated": false
      }
    ]
  }
}

FAQ

How do I test a Bitbucket webhook?

Create a free bin on webhooks.sh, add its ingest URL as a webhook in your repository settings, and push a commit or open a pull request. The delivery shows up live in your browser with its headers and JSON body, with no signup required and room for 100 requests a day for 48 hours.

How do I see a Bitbucket webhook secret after saving it?

You can't. Bitbucket doesn't display or return the secret once you save the webhook. You have to edit the webhook to enter or generate a new secret, then update the secret in every service that receives it.

Which header carries the Bitbucket webhook signature?

X-Hub-Signature carries the signature, formatted as sha256= followed by the hex HMAC-SHA256 digest of the raw request body keyed with your secret. Bitbucket only sends this header if you configured a secret on the webhook.

Does Bitbucket retry failed webhooks?

Yes, if your server returns a 500 to 599 HTTP status, Bitbucket resends the webhook up to two more times. Requests time out after 10 seconds, and the X-Attempt-Number header tells you which attempt you're looking at.

Why is X-Event-Key redacted in my bin?

The webhooks.sh viewer automatically redacts any header whose name contains key, which catches X-Event-Key. To tell events apart, give each webhook its own URL path (like /push or /pr) or check whether the JSON body has a push, pullrequest, or issue key.

Why isn't my Bitbucket webhook firing?

Check that the webhook has Active checked and the right triggers selected, and turn on request history in Bitbucket to look for TIMEOUT or NETWORK ERROR statuses (our troubleshooting guide covers common delivery failures). Also keep in mind that Bitbucket payloads over 256 KB can silently fail to trigger at all.

Last updated

Try it in 10 seconds.

Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.