SonarQube Automation with Native Tools: Quality Gates, Webhooks, and the Web API
There is one setting in SonarQube that decides everything else: the new-code period. Get it right and the quality gate judges this sprint — the code your team wrote in the last two weeks. Get it wrong and the gate judges the last five years, drowns every pull request in inherited debt, and trains your team to click "override" on reflex. Most gate-red noise traces back to that single misconfiguration, which is why every SonarQube automation effort should start there.
This is part two of our CI/CD reliability series. In part one we walked the failure patterns — gates blocking releases for issues nobody triages, misconfigured new-code definitions, debt numbers reported quarterly but never trended, hotspots rotting unreviewed. This article is the hands-on fix: how to build SonarQube automation that teams actually respect, using only the native features — quality gates, the new-code period, webhooks into CI, the Web API, and portfolio views. No third-party tooling, no plugins beyond what ships. Budget an afternoon for the first pass.
We'll work feature by feature, because that's how you'll actually do it.
Feature 1: The quality gate — make it about new code
SonarQube's default gate ("Sonar way") is deliberately built around Clean as You Code: it only judges code changed within the new-code period, leaving your legacy debt out of the pass/fail decision (Sonar docs). This is the right model. The mistake is overriding it with conditions on overall code.
Open Quality Gates → Sonar way → Copy and build your own from it. Keep every condition scoped to "On New Code":
| Condition (on new code) | Sensible value |
|---|---|
| Coverage | is less than 80% |
| Duplicated Lines (%) | is greater than 3% |
| Maintainability Rating | is worse than A |
| Reliability Rating | is worse than A |
| Security Rating | is worse than A |
| Security Hotspots Reviewed | is less than 100% |
The condition that matters most is the one that isn't there: no condition on overall coverage or overall issues. The moment you add "Overall Coverage is less than 80%" to a ten-year-old codebase, every PR fails for debt the author didn't write, and the gate becomes theater. Judge the diff, not the history.
Set the new-code period correctly
This is the setting from the opening. Per project: Project Settings → New Code. Your options:
- Previous version — new code is everything since the last
sonar.projectVersion. Best for projects with real release tagging. - Number of days — a rolling window (e.g. 30). Good for trunk-based teams that don't cut versions.
- Reference branch — new code is the diff against
main. The right default for feature-branch workflows; the gate then judges exactly what the PR adds.
Set a sane global default under Administration → New Code, then override per project where the release cadence differs. If the gate is judging code from 2021, this is where it's misconfigured.
Feature 2: Webhooks — fail the pipeline properly
Analysis is asynchronous. When your CI runs the scanner, it uploads results and returns immediately — before the gate is computed. So a naive pipeline reports green while the gate is still red. You need to wait for the gate, and the clean way is a webhook plus the report-task file the scanner writes.
Register a webhook at Administration → Configuration → Webhooks (global) or per project. SonarQube POSTs the gate result to your URL when analysis finishes; the payload includes qualityGate.status of OK or ERROR (Sonar docs).
If you don't have an endpoint to receive webhooks, poll instead. The scanner writes .scannerwork/report-task.txt containing a ceTaskId; poll the Compute Engine task, then read the gate:
# 1. Grab the task URL the scanner left behind
CE_TASK_ID=$(grep ceTaskId .scannerwork/report-task.txt | cut -d= -f2)
# 2. Poll until the background analysis task finishes
until [ "$(curl -s -u "$SONAR_TOKEN:" \
"$SONAR_HOST/api/ce/task?id=$CE_TASK_ID" \
| jq -r '.task.status')" = "SUCCESS" ]; do
sleep 5
done
# 3. Read the gate status and fail the build on ERROR
STATUS=$(curl -s -u "$SONAR_TOKEN:" \
"$SONAR_HOST/api/qualitygates/project_status?projectKey=my-project" \
| jq -r '.projectStatus.status')
echo "Quality gate: $STATUS"
[ "$STATUS" = "OK" ] || { echo "Gate failed"; exit 1; }
GitHub Actions users get this for free — the official sonarqube-quality-gate-action waits on the report-task file and fails the job on a red gate, so you don't hand-roll the poll (action docs). Either way, the rule is the same: the pipeline must not go green until the gate is green.
Feature 3: The Web API — trend debt instead of reporting it once
The console shows you today. The Web API shows you the trend, and trend is the entire point of technical debt tracking. Authenticate every call with a user token (-u "$SONAR_TOKEN:" — note the trailing colon so curl sends an empty password).
Debt over time with the measures history endpoint
api/measures/search_history returns a metric's value at every analysis. Pull the maintainability effort (sqale_index, in minutes) and the issue count across the last hundred analyses:
curl -s -u "$SONAR_TOKEN:" \
"$SONAR_HOST/api/measures/search_history?component=my-project&metrics=sqale_index,bugs,vulnerabilities,code_smells&ps=100" \
| jq '.measures[] | {metric: .metric, series: [.history[] | {date: .date, value: .value}]}'
A rising sqale_index week over week is debt drifting. A flat line while you ship features is Clean as You Code working. This is the number to put on a dashboard — not a one-off quarterly screenshot.
Issues search with the filters that separate signal from noise
Three hundred new issues is not a to-do list; it's a pile you have to sort. api/issues/search takes the filters that do the sorting for you (docs):
# Open BUG-type issues, blocker/critical severity, created in the last 14 days
curl -s -u "$SONAR_TOKEN:" \
"$SONAR_HOST/api/issues/search?componentKeys=my-project&types=BUG&severities=BLOCKER,CRITICAL&statuses=OPEN&createdInLast=14d&ps=100" \
| jq -r '.issues[] | "\(.severity)\t\(.component)\t\(.message)"'
Filter by types=BUG first — a real reliability bug is worth a human's attention in a way a CODE_SMELL naming convention rarely is. Narrow by severities and createdInLast to get from 300 issues down to the dozen that could actually break production this sprint.
Hotspot review status — the thing that rots
Security hotspots aren't issues; they live in their own endpoint and their own review workflow. Unreviewed hotspots are the most commonly ignored surface in SonarQube. List everything still TO_REVIEW:
curl -s -u "$SONAR_TOKEN:" \
"$SONAR_HOST/api/hotspots/search?projectKey=my-project&status=TO_REVIEW&ps=100" \
| jq -r '.hotspots[] | "\(.securityCategory)\t\(.vulnerabilityProbability)\t\(.component)"'
Sort your review effort by vulnerabilityProbability (HIGH first). A hotspot the gate demanded be reviewed at 100% but which sits at TO_REVIEW for weeks is a gate condition your team is quietly routing around.
Feature 4: Portfolios and applications — debt across projects
One project's sqale_index is a data point. The debt story lives at the portfolio level, where you see which of forty services is drifting. Portfolios are a commercial-edition feature (Enterprise and above) configured under Administration → Configuration → Portfolios (docs); an Application groups projects that ship together so the gate can judge them as a unit.
Pull the same measures across a portfolio's children to rank debt:
curl -s -u "$SONAR_TOKEN:" \
"$SONAR_HOST/api/measures/component_tree?component=my-portfolio&metrics=sqale_index,reliability_rating,security_rating&s=metric&metricSort=sqale_index&ps=100" \
| jq -r '.components[] | "\(.name)\t\(.measures[]?.value // "-")"'
On Community Edition you don't have portfolios — loop the single-project history call over your project list and aggregate the CSV yourself. It's more work, but the trend data is identical.
Feature 5: PR decoration — put the gate where the review happens
Engineers respect a gate they see at review time, not in a separate SonarQube tab they never open. PR decoration posts the gate result and new issues directly onto the pull request. Configure it under Project Settings → General Settings → DevOps Platform Integration, bind the project to your provider (GitHub, GitLab, Azure DevOps, Bitbucket), and pass the PR parameters to the scanner (docs):
sonar-scanner \
-Dsonar.pullrequest.key=42 \
-Dsonar.pullrequest.branch=feature/checkout-retry \
-Dsonar.pullrequest.base=main
Now the gate summary and each new issue land as a check and inline comments on the PR. Combined with the reference-branch new-code period, the reviewer sees exactly one thing: what this change added. That's the whole "respect the gate" mechanism in one screen.
The honest part: where native SonarQube stops
Everything above is worth doing, and it gets you a gate teams respect, debt you can trend, and hotspots you can find. Here is what it does not get you.
The gate decides pass or fail. It does not decide what matters. When a PR lights up with 300 new issues, SonarQube ranks them by severity — but severity is not priority. Deciding that these twelve are real bugs, those forty are style noise your team doesn't care about, and this cluster is a false-positive pattern from a rule that fights your framework — that is human judgment, every single PR. The API hands you the list; it does not triage it.
Trend data is not a plan. search_history shows you which of your forty services is drifting. It won't tell you which drift is a monolith you're deliberately strangling versus a service quietly rotting. It won't schedule the debt-repayment sprint, size it, or argue for it against feature pressure in planning. Someone owns that conversation, and it's not a webhook.
Detection isn't remediation, and nobody's watching between analyses. The gate fires on a push. Between pushes, the hotspot backlog grows, the false-positive rules keep firing, and the person who was going to triage the issue list got pulled onto an incident. The native tooling is event-driven and stateless; it has no memory that last week's 300 issues were never triaged. That standing backlog is exactly where quality theater regrows.
None of this makes the native setup worthless — it makes it the floor. The question it always leads to: who watches the gate results and the issue inflow continuously, and does the triage?
What comes next
Every check in this guide can run continuously, with the triage attached. CloudThinker agents connect to SonarQube read-only, watch gate results and new-issue inflow across every project, separate real bugs from style noise and false-positive candidates, trend each project's debt to flag the ones drifting, and propose triage lists and debt-sprint candidates under your approval. That's part three of this series.
Or see your own projects' findings first: Try CloudThinker free — 100 premium credits, no card required.
