Bitbucket is the least obvious of the major Git platforms for CLA enforcement, and for good reason: it has no native CLA integration and its Pipelines model differs fundamentally from GitHub Actions or GitLab CI in ways that affect how you wire external compliance checks. Most teams running Bitbucket as their primary SCM have either skipped CLA enforcement entirely or built something bespoke that covers their main repository but doesn't scale.
This guide covers what Bitbucket gives you natively, where it falls short, and how to build a working CLA enforcement flow using Bitbucket's webhook system, Merge Checks API, and pipeline integration.
Bitbucket's Integration Model for CLA Enforcement
Bitbucket Cloud offers three mechanisms relevant to CLA enforcement:
- Webhooks: Workspace-level or repository-level event notifications. You can subscribe to pull request events (created, updated, approved, declined, fulfilled) and receive payload data at your webhook endpoint.
- Merge Checks: Bitbucket's equivalent of GitHub's required status checks. A merge check is an external status registered via API that blocks the "Merge" button if it's in a non-passing state. This is the primary enforcement mechanism.
- Bitbucket Pipelines: CI/CD running inside Bitbucket. A pipeline step can fail, which blocks merge for PRs configured to require passing pipelines. This is an alternative (less preferred) enforcement path.
Bitbucket Server (self-managed) has a different API surface and requires a marketplace app or a bespoke plugin for merge check integration. This guide focuses on Bitbucket Cloud.
Setting Up Workspace-Level Webhooks
For org-level CLA coverage, configure a single webhook at the workspace level rather than per-repository. Workspace webhooks in Bitbucket Cloud can be configured through the workspace Settings → Webhooks interface or via the Bitbucket API:
POST https://api.bitbucket.org/2.0/workspaces/{workspace}/hooks
{
"description": "CLA check webhook",
"url": "https://your-cla-service.com/webhooks/bitbucket",
"active": true,
"secret": "your-webhook-secret",
"events": [
"pullrequest:created",
"pullrequest:updated",
"pullrequest:comment_created"
]
}
The workspace webhook fires for all repositories in the workspace. The payload includes the repository slug, PR ID, and author information. Bitbucket's webhook secret mechanism uses HMAC-SHA256 signing; always validate the X-Hub-Signature header in your handler.
The Merge Check API
Bitbucket's Commit Statuses API (also called the Build Status API) is what you use to post CLA check results. Despite the "build status" name, it's used for any external check that should block or inform a PR merge:
POST https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/commit/{commit}/statuses/build
{
"state": "INPROGRESS" | "SUCCESSFUL" | "FAILED" | "STOPPED",
"key": "cohorto-cla",
"name": "CLA Check",
"url": "https://your-cla-service.com/sign?pr={pr_id}",
"description": "2 of 3 contributors require CLA signature"
}
The key field is the stable identifier for your check. Like GitHub's context field, changing this key breaks any merge requirement policies that reference it. Keep it stable and treat it as a versioned identifier.
To make this status check actually block merges, you need to configure a merge check requirement on the target branch. In Bitbucket Cloud, go to Repository Settings → Branch restrictions → Edit the restriction for your default branch and add a "Require passing builds for merge" rule with your check's key. Note that this feature requires Bitbucket Cloud Premium.
Extracting PR Authors in Bitbucket
Bitbucket's PR payload includes the PR author, but not the full commit author list. To get all commit authors in a PR:
GET https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pr_id}/commits
This returns paginated commit objects. Each commit has an author field with user (if the email is linked to a Bitbucket account) and raw (the free-form git author string). The same email-to-account resolution challenge that exists on GitLab applies here: commits authored with an email not associated with a Bitbucket account return a null user object.
Bitbucket's account email lookup is more restricted than GitLab's — you can only retrieve email associations for accounts where the user has made their email public, unless you're using an admin API token with elevated access. For workspace-internal contributors, workspace admin access typically resolves this. For external contributors using personal accounts, you'll often only have the git commit email to work with.
Bitbucket Pipelines as a Fallback Enforcement Path
If you're on Bitbucket Standard (not Premium) and can't use merge check requirements, a Pipelines-based CLA check is a viable fallback. The pipeline step calls your CLA service and exits with a non-zero code if any contributor hasn't signed:
image: alpine:3.19
pipelines:
pull-requests:
"**":
- step:
name: CLA Check
script:
- apk add --no-cache curl jq
- |
# Collect PR commit authors
COMMITS=$(curl -s -u ${BITBUCKET_USER}:${BITBUCKET_APP_PASSWORD} \
"https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_FULL_NAME}/pullrequests/${BITBUCKET_PR_ID}/commits")
# Check each author against CLA service
# Exit 1 if any are unsigned
The Pipelines fallback has the same tradeoffs as the GitLab CI alternative: it consumes pipeline minutes, queues behind other pipeline steps, and requires the pipeline runner to have network access to your CLA service. For organizations with Bitbucket Premium, the webhook + merge check path is preferable.
Bitbucket Data Center (Server) Considerations
Bitbucket Data Center uses a plugin architecture (Atlassian Marketplace apps or custom plugins built with the Bitbucket Server SDK) for merge pre-receive hooks and build integrations. The integration patterns differ significantly from Bitbucket Cloud:
- Pre-receive hooks run server-side before any content is accepted; post-receive hooks run after. CLA enforcement can be implemented as a pre-receive hook that calls an external service, but this introduces latency into every push operation.
- The Bitbucket Server REST API supports a "required builds" mechanism for pull requests similar to Cloud's merge checks, but the configuration and key format differ.
- Bitbucket Data Center instances running in network-isolated environments may require CLA service deployment within the same network, or a proxy/relay configuration for webhook delivery.
We're not saying Bitbucket Data Center CLA integration is excessively complex — but it requires planning for the server-side execution environment in a way that Bitbucket Cloud or GitHub App integrations don't. If your organization is evaluating platforms partly on CLA tooling integration cost, Cloud's webhook + merge check path is meaningfully simpler to operate.
Workspace Admin Token Configuration
Your CLA service needs a Bitbucket workspace admin token to post commit statuses and resolve contributor account identities. Use a dedicated service account (not a personal account) for the integration. Configure the token with the minimum required OAuth scopes:
pullrequest— read access to PRs and their commitsrepository— read access to commit dataaccount:email— access to linked email addresses (for contributor identity resolution)- Build Status API write access — to post check results
Token rotation is an operational consideration that's easy to overlook: if your service account's token expires or is revoked, CLA checks stop posting results and PRs may merge without CLA verification depending on how your merge requirements are configured. Monitor for failed webhook deliveries (Bitbucket logs these in the workspace webhook settings) and alert on delivery failure rates as a secondary signal that the integration is functioning correctly.
The Bitbucket integration is more assembly-required than GitHub's native app ecosystem, but the core components — workspace webhooks, commit status API, and merge check requirements — give you the building blocks for a functional CLA enforcement system that operates consistently across all repositories in your workspace.