Bitbucket Webhook: Example Payload, Headers and Signature

Bitbucket Cloud webhooks look like GitHub's at first glance and differ in the details that break copied code: the event name header, the signature header's name, and the shape of a push. Here is a repo:push delivery.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example below to your webhook URL from your browser. It is not sent by Bitbucket, it carries your browser's User-Agent, and its signature is a placeholder that will not verify. To see a real delivery, follow the steps further down.

Last updated . How this page is checked.

How the webhook URL works. It is a public address that records whatever is sent to it and answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is deleted 24 hours after it was created — or sooner if you press Delete URL or our server restarts, because requests are held in memory and never written to disk.

Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.

What is a Bitbucket webhook?

A Bitbucket webhook is an HTTP POST sent to a URL configured on a repository or workspace when a chosen trigger fires: a push, a pull request being created or merged, a comment, a build status change. The trigger travels in the X-Event-Key header in the form repo:push or pullrequest:created; the JSON body does not repeat it.

Every body starts with an actor (who did it) and a repository. What follows depends on the event. For a push it is a push.changes array with one entry per branch or tag that moved, each holding the old and new state of that reference.

What a Bitbucket webhook request looks like

The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.

POST /your/webhook/path HTTP/1.1
Host: your-server.example
X-Event-Key: repo:push
X-Hook-UUID: b2a1e7c4-5d0f-4e2b-9a3c-0d6f1e8b7a55
X-Request-UUID: 0c3f7d2e-8b1a-4f6d-a9e5-2c4b6d8f0a13
X-Attempt-Number: 1
X-Hub-Signature: sha256=4f8b1e0c7a2d9e6b3c5f0a1d8e7b6c4f2a9d0e3b1c8f7a6d5e4b3c2a1f0e9d8c
Content-Type: application/json
User-Agent: Bitbucket-Webhooks/2.0
{
  "actor": {
    "type": "user",
    "display_name": "Ada Lovelace",
    "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}",
    "account_id": "5d5355e8c6b9320d9ea5b28d"
  },
  "repository": {
    "type": "repository",
    "name": "hello-world",
    "full_name": "acme/hello-world",
    "uuid": "{673a6070-3421-46c9-9d48-90745f7bfe8e}",
    "is_private": true
  },
  "push": {
    "changes": [
      {
        "created": false,
        "closed": false,
        "forced": false,
        "truncated": false,
        "old": {
          "type": "branch",
          "name": "main",
          "target": {
            "type": "commit",
            "hash": "6113728f27ae82c7b1a177c8d03f9e96e0adf246"
          }
        },
        "new": {
          "type": "branch",
          "name": "main",
          "target": {
            "type": "commit",
            "hash": "59b20b8d5c6ff8d09518454d4dd8b7b30f095ab5",
            "message": "Fix retry backoff in webhook worker\n",
            "date": "2026-01-01T12:00:00+00:00",
            "author": {
              "type": "author",
              "raw": "Ada Lovelace <ada@example.com>"
            }
          }
        },
        "commits": [
          {
            "type": "commit",
            "hash": "59b20b8d5c6ff8d09518454d4dd8b7b30f095ab5",
            "message": "Fix retry backoff in webhook worker\n"
          }
        ]
      }
    ]
  }
}

One push can move several references, so changes is a list. A new branch has created true and old null; a deleted branch has closed true and new null. The commits list is capped at the most recent few, with truncated set when there were more, so use the API when you need the full set. UUIDs arrive wrapped in curly braces.

Headers Bitbucket sends

HeaderExampleWhat it is
X-Event-Keyrepo:pushThe trigger that fired. Read this to know how to interpret the body.
X-Hook-UUIDb2a1e7c4-5d0f-4e2b-9a3c-0d6f1e8b7a55Id of the webhook configuration.
X-Request-UUID0c3f7d2e-8b1a-4f6d-a9e5-2c4b6d8f0a13Id of this event. It stays the same across retry attempts.
X-Attempt-Number1Which delivery attempt this is, starting at 1.
X-Hub-Signaturesha256=4f8b1e0c7a2d9e6b3c5f0a1d8e7b6c4f2a9d…HMAC-SHA256 of the body, hex, prefixed sha256=. Present only when the webhook has a secret.
Content-Typeapplication/jsonThe body is JSON.
User-AgentBitbucket-Webhooks/2.0Identifies the sender.

Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.

How Bitbucket signs a webhook

Give the webhook a secret and Bitbucket computes an HMAC-SHA256 of the request body with it, sending sha256= followed by the hex digest in X-Hub-Signature. The calculation is the same one GitHub uses; the header name is not, since GitHub's SHA-256 header ends in -256. No timestamp is signed, so the signature shows origin and integrity but not freshness.

Compute it over the raw request body exactly as received — before any JSON parsing — and compare with a constant-time function. Without a secret there is no signature header at all, and a handler that skips the check when the header is missing can be driven by anyone who knows the URL.

const crypto = require('crypto');

// rawBody: Buffer or string of the body exactly as received
function verifyBitbucket(rawBody, header, secret) {
  const expected = 'sha256=' +
    crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(header || '');
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Timeouts, attempts and the request history

Bitbucket waits about ten seconds for a response, per its documentation at the time of writing. If the endpoint times out or answers with a 5xx, the same event is attempted again, up to two more times, with X-Attempt-Number counting upwards and X-Request-UUID unchanged. A 4xx is treated as a final answer and is not retried.

Each webhook has a request history page listing recent deliveries with the request and response, and a failed one can be re-sent from there. The history has to be switched on first, and it is the quickest way to see whether Bitbucket attempted a delivery at all.

Catch a real Bitbucket webhook

  1. Press Create webhook URL at the top of this page and copy the address.
  2. In the repository, open Repository settings → Webhooks → Add webhook.
  3. Give it a title, paste the address as the URL, and enter a throwaway secret if you want to see X-Hub-Signature.
  4. Choose the triggers — repository push is the default — and save.
  5. Push a commit and read the delivery in the inbox above.

To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.

Things that catch people out

  • GitHub handlers need two edits. Read X-Event-Key instead of X-GitHub-Event, and X-Hub-Signature instead of X-Hub-Signature-256.
  • Data Center is a different product. Self-hosted Bitbucket uses other event keys, such as repo:refs_changed, and a different payload.
  • A push is not one commit. Loop over changes, and over commits inside each, and handle new being null.

api-tester.com is independent and is not affiliated with Bitbucket. Providers change header names, limits and retry schedules; treat the official Bitbucket documentation as the source of truth.

Related examples and tools

Other webhooks signed with an HMAC of the body

References

These links leave api-tester.com, which is independent of the companies and projects they point to.

Bitbucket webhook FAQ

What does a Bitbucket webhook payload look like?

A JSON object with an actor and a repository, followed by event-specific data. For repo:push that is a push.changes array, where each change has the old and new state of a branch or tag and a list of recent commits.

Where is the event type in a Bitbucket webhook?

In the X-Event-Key header, for example repo:push or pullrequest:fulfilled. The body does not include it.

How do I verify a Bitbucket webhook?

Set a secret on the webhook, compute an HMAC-SHA256 of the raw request body with it, hex-encode the result, prefix it with sha256= and compare it to the X-Hub-Signature header in constant time.

Does Bitbucket retry failed webhooks?

Yes, for timeouts and 5xx responses: up to two further attempts according to Bitbucket's documentation at the time of writing. The X-Attempt-Number header shows which attempt a request is.

What is the difference between Bitbucket and GitHub webhooks?

Both post JSON and sign the body with HMAC-SHA256, but the header names differ (X-Event-Key and X-Hub-Signature on Bitbucket), the push payload is structured around a changes array, and Bitbucket retries some failures automatically.