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
| Header | Example | What it is |
|---|---|---|
X-Event-Key | repo:push | The trigger that fired. Read this to know how to interpret the body. |
X-Hook-UUID | b2a1e7c4-5d0f-4e2b-9a3c-0d6f1e8b7a55 | Id of the webhook configuration. |
X-Request-UUID | 0c3f7d2e-8b1a-4f6d-a9e5-2c4b6d8f0a13 | Id of this event. It stays the same across retry attempts. |
X-Attempt-Number | 1 | Which delivery attempt this is, starting at 1. |
X-Hub-Signature | sha256=4f8b1e0c7a2d9e6b3c5f0a1d8e7b6c4f2a9d… | HMAC-SHA256 of the body, hex, prefixed sha256=. Present only when the webhook has a secret. |
Content-Type | application/json | The body is JSON. |
User-Agent | Bitbucket-Webhooks/2.0 | Identifies 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
- Press Create webhook URL at the top of this page and copy the address.
- In the repository, open Repository settings → Webhooks → Add webhook.
- Give it a title, paste the address as the URL, and enter a throwaway secret if you want to see
X-Hub-Signature. - Choose the triggers — repository push is the default — and save.
- 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-Keyinstead ofX-GitHub-Event, andX-Hub-Signatureinstead ofX-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 overcommitsinside each, and handlenewbeing 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
Jira Webhook Example
A Jira webhook delivers the whole issue plus a changelog of exactly what was edited.
Mailgun Webhook Example
Mailgun puts its signature inside the JSON body instead of in a header, and that signature covers a timestamp and a token rather than the event itself.
Typeform Webhook Example
A Typeform webhook sends one form submission: the questions as they were defined and the answers as they were given, in two separate arrays you have to join yourself.
Calendly Webhook Example
Calendly webhooks are created through the API rather than a settings page, and they describe everything by URI.
Linear Webhook Example
A Linear webhook reports a change to one entity — an issue, a comment, a project — and, on updates, tells you the previous values of whatever changed.
GitHub Webhook Example
The same push from GitHub: a different event header and a signature header ending in -256.
Curl POST JSON
Send a JSON body like this one from a terminal.
Webhook Tester
The inbox on its own page, plus a guide to signatures, retries and idempotent handlers.
All Webhook Examples
30 providers side by side: content type, signature scheme and retry behaviour.
Other webhooks signed with an HMAC of the body
References
- Official Bitbucket documentation — The provider's own guide to the requests described on this page.
- Bitbucket Cloud: event payloads — The payload reference for every event.
- Bitbucket Cloud REST API: webhooks — Managing webhooks over the API.
- RFC 2104: HMAC — The keyed-hash construction this signature is built on.
- Node.js: crypto.timingSafeEqual() — The constant-time comparison used in the code on this page.
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.