# GitLab webhooks

Source: https://codeherder.com/docs/gitlab-webhooks/

How CodeHerder registers a GitLab project webhook for a repo, how to set one up by hand, and how to rotate, remove and troubleshoot it.

A **GitLab webhook** makes GitLab push merge request, comment, pipeline, job, push, tag and release events to CodeHerder. CodeHerder then does not need to poll GitLab for them. Each repo has its own hook and its own secret.

## What you need

CodeHerder registers a hook through the GitLab API. The GitLab connection token must be able to do that. It needs the `api` scope and the Maintainer role on the project. A group access token or a project access token works.

The same connection token lets the server mint project tokens for your agents. The device holds no GitLab mint token. For each sandbox, the server creates a short-lived project access token with this connection, and revokes it when the sandbox ends. A connection token without the `api` scope and the Maintainer role cannot create project tokens. The device then uses its own token pool for GitLab, as it did before. On GitLab.com, project access tokens need the Premium or Ultimate plan. On a Free namespace, GitLab refuses the create call with HTTP 400, whatever the token’s scope and role. The server counts that refusal as a failed mint, so the device serves no GitLab credential and does not use its pool. The server log names GitLab’s reason. See [Git credentials on a device](https://codeherder.com/docs/git-tokens-on-a-device/).

The connection’s **Sandbox git tokens** setting controls this. `project_access_token` is the default, and a connection without the setting uses it. An owner can set `off`. The server then mints no project token for that connection, and each device uses its own GitLab credential, such as its token pool. Any other value is a failed mint. To turn minting off, edit the GitLab connection in **Integrations**, set **Sandbox git tokens** to `off`, and save. The server still revokes the tokens it minted before.

A token with only the `read_api` scope can still read merge state. It cannot create a hook. In that case CodeHerder shows manual setup steps. See [Set up a hook by hand](https://codeherder.com/docs/gitlab-webhooks/#set-up-a-hook-by-hand).

The `api` scope is optional. It gives the token write access to GitLab. Use it only if you want one-step setup. See [Integrations](https://codeherder.com/docs/integrations/) to connect GitLab.

## Automatic setup

1. Connect GitLab in **Settings → Integrations**. Use a token with the `api` scope.
2. Add a GitLab repo to your workspace. See [Connecting repositories](https://codeherder.com/docs/repositories/).
3. CodeHerder creates the hook in the background. Adding the repo does not wait for it.

CodeHerder also creates the hook when you restore an archived repo. It moves the hook when you change the repo’s git URL.

Open the repo page to see the **Webhook** section. It shows the status and the last error. An admin can click **Register**, **Rotate** or **Remove** there. You can do the same from the CLI:

```
ch repo hook show <repo>
ch repo hook register <repo>
ch repo hook rotate <repo>
ch repo hook remove <repo>
```

**Register** is safe to repeat. CodeHerder deletes any hook that already points at its receive URL, so GitLab never holds two copies.

## Set up a hook by hand

Click **Register** when the token cannot create hooks. CodeHerder then shows the receive URL, a secret and a list of steps. The secret appears once. Copy it now. CodeHerder cannot show it again. If you lose it, click **Rotate** for a new one. A later **Try automatic setup** keeps the current secret, so a hook you set up by hand keeps working. It shows the steps again, but not the secret.

1. In GitLab, open the project. Go to **Settings**, then **Webhooks**.
2. Choose **Add new webhook**. Paste the receive URL into the **URL** field.
3. Paste the secret into the **Secret token** field.
4. Turn on these triggers: Push events, Tag push events, Comments, Merge request events, Job events, Pipeline events, Releases events.
5. Keep **Enable SSL verification** on. Save the webhook.

## Rotate the secret

Click **Rotate** or run `ch repo hook rotate`. CodeHerder makes a new secret and replaces the old one on both sides. In manual mode, CodeHerder shows the new secret once. Paste it into the GitLab webhook.

A delivery that is in flight during the change can fail verification. GitLab sends later events with the new secret.

## Remove the hook

Click **Remove** or run `ch repo hook remove`. CodeHerder deletes the GitLab hook, the stored secret and the hook state. Later deliveries to the receive URL get a 401 answer.

If GitLab refuses the delete, CodeHerder still removes its own side. The answer names the hook id. Delete that hook by hand in **Settings → Webhooks**.

Archiving a repo removes its hook. Deleting a workspace does not. The receive URL then answers 401, and GitLab disables the hook after repeated failures. Delete the hook by hand if you want it gone sooner.

## Self-managed GitLab

Set the **Host** field of the GitLab connection to your GitLab hostname. Use the bare name, for example `gitlab.example.com`. The flow is the same as for gitlab.com.

Your GitLab instance must reach the server’s public base URL (`CH_BASE_URL`). If CodeHerder runs on a private network, an instance admin must turn on **Allow requests to the local network from webhooks and integrations**. The setting is under **Admin → Settings → Network → Outbound requests**.

CodeHerder also refuses to call a private GitLab host unless the server allows internal hosts. In that case it falls back to manual setup.

## Request changes on a merge request

CodeHerder records a GitLab “request changes” review as `repo.merge_request_changes_requested`. GitLab 17 or later sends it in the merge request hook, as the reviewer’s state `requested_changes`.

1. Open the merge request in GitLab as the reviewer.
2. Select **Your review**, or **Start review**.
3. Select **Request changes**.
4. Select **Submit review**.

Use the web UI. A Developer-role token could not set this state through the REST or GraphQL API. A review summary note arrives as a separate comment event.

An approval adds the approver to the reviewer list. CodeHerder does not read that as a review request.

## Troubleshooting

The **Webhook** section shows one of these last errors.

| Last error | What to do |
| --- | --- |
| no GitLab token that can create webhooks is connected for this host | Connect GitLab for this host. Use a token with the `api` scope. Or set up the hook by hand. |
| GitLab refused the webhook request; the token needs the api scope and the Maintainer role | Give the token the `api` scope and the Maintainer role. Then click **Register**. |
| CodeHerder may not call this GitLab host | The host is private or not allowed. Set up the hook by hand. |
| GitLab did not answer the webhook request | Check that GitLab is up. Then click **Register** again. |
| GitLab answered the webhook request with an error | Check the GitLab status. Then click **Register** again. |
| secret out of sync; rotate again | GitLab holds a new secret and CodeHerder holds the old one. Click **Rotate**. |
| the server has no public base URL (CH_BASE_URL), so GitLab has no address to post to | Ask the server operator to set `CH_BASE_URL`. |

If GitLab shows failed deliveries with a 401 status, the secrets do not match. Click **Rotate**.

### Check the repo’s integration health

The repo page has an **Integration health** section. `ch repo health <repo>` shows the same data. It reads **Healthy** when deliveries arrive and nothing is missing. It reads **Gap** when GitLab shows merge request activity that no delivery announced. It reads **Failing** when deliveries arrive and fail. It reads **Setup failed** when the hook is not registered. For a gap, open **Settings → Webhooks** in GitLab, edit the CodeHerder webhook, open **Recent events**, and resend the missed delivery. The section links to the hook when CodeHerder knows its id. While the status is not healthy, CodeHerder reads the repo from GitLab every 30 seconds, so task state stays correct.
