# SonarQube Quality Gates That Run a SOP

SonarQube calls a webhook after each analysis, with the project, the branch or pull request, and
the quality gate's verdict and conditions. Pointed at the gateway, that call runs a SOP: when the
quality gate fails (the default), or after every analysis.

```mermaid
flowchart LR
    CI["<b>CI Runs an Analysis</b>"] --> WH["<b>SonarQube Webhook</b><br/>project or global"]
    WH -->|"POST (signed)"| GW["<b>/goclaw/hook/sonarqube/&lt;key&gt;</b>"]
    GW --> SOP["<b>SOP Run on the Agent</b>"]
```

There are two ways to set it up:

- **Ask the agent.** An admin describes it in a conversation. The agent's `sonarqube_hook` tool
  creates the webhook in SonarQube and the route in GoClaw, and removes both together. See
  [Asking for a hook](#asking-for-a-hook).
- **Connect it yourself.** Make a `sonarqube` route on the **Webhooks** page, and a webhook in
  SonarQube with the URL and secret the page shows. See
  [Connecting SonarQube without the agent](#connecting-sonarqube-without-the-agent).

---

## Before you start

| What | Where |
| :--- | :--- |
| The SonarQube instance, with `allow_write: true` (only when the agent sets hooks up) | `tools.sonarqube` (or `tools.sonarqubes.<name>`): the **Integrations** page or `config.yaml` |
| A **user** token with **Administer** on the project (global **Administer** for a hook on every project) | the instance's `token`; analysis tokens cannot manage webhooks |
| An address SonarQube can reach the gateway at | `gateway.ingress.url`, and a listener there |
| A SOP for the deliveries to run | the SOPs page, or the agent's `sop_save` tool ([Standard Operating Procedures](sop.md)) |

```yaml
gateway:
  bind: lan
  ingress:
    url: "http://192.0.2.10:8080"     # as SonarQube sees this gateway

tools:
  sonarqube:
    url: "https://sonar.example.com"
    token: "${secret:SONARQUBE_TOKEN}"
    allow_write: true                 # off by default: the agent cannot create or remove hooks
```

The SonarQube host needs no entry on `tools.http.allow`: the SonarQube tools may reach the
instance you configured.

**SonarQube and local addresses.** SonarQube refuses a webhook to its own host and to loopback,
and SonarQube Community Build 26.10 and later refuse private addresses (`10.x`, `192.168.x`) as
well, unless its admin unticks **Enable local webhooks validation**
(**Administration › Configuration › General Settings › Security**, `sonar.validateWebhooks`). The
plan says so when the gateway's address is such an address, and SonarQube's refusal is passed on
with the same advice.

---

## Asking for a hook

```
When the quality gate of backend fails, run the quality-triage SOP.
Run release-check after every analysis of backend. Call it "Backend analyses".
Run gate-report whenever any project's quality gate fails.
```

The agent answers with a plan; nothing is created until you confirm it in your next message:

```
Plan 7c41a9d2 — nothing is created yet.

SonarQube "default" (version 10.6.0.92116), project backend (Backend), gets a webhook "GoClaw: Backend quality gate":
- after every analysis SonarQube calls; the SOP runs when the quality gate fails
- SonarQube calls http://192.0.2.10:8080/goclaw/hook/sonarqube/backend-quality-gate, signing each delivery with a new secret only this route knows
- SonarQube has no test call: the hook is first seen working after the next analysis
GoClaw: each delivery runs SOP "quality-triage" (Quality triage) as agent main.
```

`on: any` runs the SOP after every analysis; `all_projects` makes a global webhook, called for
every project, in place of one project's.

The plan is refused, with the reason, when the project does not exist for the token, the token
lacks Administer, SonarQube already has a webhook to that route or already has its 10 webhooks
there, the route key is taken, the instance has no token, or it does not allow writes.

---

## What the SOP is given

Each delivery is the run's trigger data, in an untrusted section of every step. The agent is told:

```
Project: Backend (backend)
Branch: main (main branch)
Quality gate: Sonar way — ERROR
  ✗ new_coverage: 42.1 (must be at least 80)
Analysed: 2026-10-09T10:00:00+0800
Revision: c739069e
Dashboard: https://sonar.example.com/dashboard?id=backend
```

Only the conditions that failed are listed. A pull request's analysis says `Pull request:` in
place of `Branch:`. One project's branch (or pull request) keeps one session.

The route's events are `failed`, `passed`, `warning` (older SonarQube) and `analysis` (no
quality gate). A hook made for failures filters on `failed`; one made for every analysis has no
filter.

A delivery SonarQube sends again — the same analysis task — is answered and not run again. A
quality gate flipped later by an issue change is its own delivery.

---

## Listing, checking, removing

- **list**: every hook this gateway registered, its project, whether it runs on failures or
  every analysis, and SonarQube's own record of its last delivery.
- **test**: SonarQube has no test call. This reports SonarQube's last delivery (when, and what
  the gateway answered) beside the deliveries the route counted. Before the first analysis it
  says there has been none.
- **delete**: planned and confirmed like a create; removes the SonarQube webhook and the route.
  Deleting the route on the **Webhooks** page removes the SonarQube webhook too, when the
  instance allows writes; otherwise it says which webhook to delete in SonarQube.

Only a human admin can do this, in a conversation of their own: never a SOP, cron, webhook or
subagent run.

---

## Connecting SonarQube without the agent

1. On the **Webhooks** page, create a route of type **SonarQube** with a key (for example the
   project's), the agent, and the SOP. Set **Events** to `failed` to run only on failed quality
   gates. Saving shows the URL and the secret once.
2. In SonarQube, open the project's **Project Settings › Webhooks › Create** (or
   **Administration › Configuration › Webhooks** for every project). **URL**: the route's URL.
   **Secret**: the route's secret (SonarQube 10.6 and later want at least 16 characters).
3. Run an analysis. The route's **Hits** go up, and SonarQube's webhook page shows the delivery.

SonarQube signs each delivery with the secret (`X-Sonar-Webhook-HMAC-SHA256`); a delivery whose
signature does not match is refused with 401, and the reason is shown on the route.

## Related

- [Graylog Alerts That Run a SOP](graylog-alerts.md) — the same pattern for Graylog alerts
- [GitLab Webhook Integration](gitlab-webhook.md) — the same for GitLab project events
- [Standard Operating Procedures](sop.md)
