GitLab
A GitLab source receives project and group webhooks. Every request becomes one event; pipelines, merge requests and pushes get a title written for them. ramzes accepts GitLab’s secret token, and also its signing token. This page covers the setup, both ways of verifying, and the exact mapping.
Set it up with a secret token
Section titled “Set it up with a secret token”-
In ramzes, open Sources. Under Add a source, choose GitLab in What sends the events, give the source a name and select Create source.
-
A card titled name is ready to receive appears. Copy the key now: it cannot be shown again. The card also shows the address to give the sender, of the form
http://localhost:7269/v1/in/<source id>. -
In GitLab, open the project’s or group’s Settings, Webhooks, Add new webhook, and fill in:
Field in GitLab Value URL the source’s address Secret token the source key -
Choose the triggers and save.
GitLab has to reach ramzes over the network. The address in the card uses the host name you opened the ramzes UI with; replace the host if GitLab reaches ramzes under another name.
Set it up with a signing token
Section titled “Set it up with a signing token”With a signing token GitLab signs each request instead of sending a shared secret in a header. The bundled setup text describes this as available from GitLab 19.0. GitLab generates the token, so you give it to ramzes:
-
Create the source and the webhook as above.
-
In GitLab, generate the webhook’s signing token and copy it. ramzes accepts it with or without its
whsec_prefix. -
In ramzes, select Replace key on the source’s row, paste the token into Secret from the sender, if it generates its own, and confirm with Replace key.
The previous key stops working at once. From then on the source key is the signing token.
How requests are verified
Section titled “How requests are verified”ramzes looks at the webhook-signature header first.
If the request has a webhook-signature header, the signature is checked and the token header is not consulted:
- The source key, without its
whsec_prefix, is decoded as standard base64. The result is the HMAC key. - ramzes computes the HMAC-SHA256 of
<webhook-id>.<webhook-timestamp>.<body>: the two header values and the raw request body, joined by full stops. - The header may hold several signatures separated by spaces. The request passes if one that starts with
v1,equals the base64 of the computed value. - The
webhook-timestampheader, in Unix seconds, must be within 5 minutes of ramzes’s clock, earlier or later.
A request missing webhook-id or webhook-timestamp fails. So does every signed request when the source key is not valid base64, which is what happens if GitLab signs but the source still holds a key ramzes generated.
If the request has no webhook-signature header, the X-Gitlab-Token header must equal the source key.
A request that fails gets 401 with the message the request's signature does not match this source's key, or the request is more than 5 minutes old, and is not recorded.
GitLab’s documentation does not say which base64 alphabet the signing token uses. ramzes assumes the standard one. This is listed on the Verified claims page.
How a request becomes an event
Section titled “How a request becomes an event”The mapping reads object_kind from the JSON body.
| Event field | Value |
|---|---|
topic |
gitlab. followed by object_kind, for example gitlab.pipeline. Without object_kind it uses event_name, then gitlab.event. |
title |
<project path>: <what happened>, see below |
severity |
error for a pipeline whose status is failed; info for everything else |
status |
resolved for a pipeline whose status is success; firing for everything else |
body |
object_attributes.url, or else the project’s web_url |
labels |
project (path with namespace), kind (object_kind), ref, user (username). A label whose value is absent is left out. |
dedup_key |
for pipelines <project path>/pipeline/<ref>; none for other events |
payload |
the whole request body |
ts is not set, so the event is stamped with the time ramzes received it.
Titles
Section titled “Titles”The title starts with the project’s path_with_namespace, or with GitLab if the payload has no project.
object_kind |
Title after the prefix | Example |
|---|---|---|
pipeline |
pipeline #<id> <status> on <ref> |
acme/shop: pipeline #31 failed on main |
merge_request |
merge request !<iid> <action>: <title>; the state is used when there is no action |
acme/shop: merge request !9 merge: Add retries |
push, tag_push |
push to <branch> by <user name> (<n> commits); a tag is shown as tag <name> |
acme/shop: push to main by Kris (4 commits) |
| anything else | the object_kind itself |
acme/shop: note |
Example
Section titled “Example”This request:
{ "object_kind": "pipeline", "project": { "name": "shop", "web_url": "https://gitlab.com/acme/shop", "path_with_namespace": "acme/shop" }, "user": { "username": "kris" }, "object_attributes": { "id": 31, "iid": 3, "ref": "main", "status": "failed", "url": "https://gitlab.com/acme/shop/-/pipelines/31" }}becomes this event:
{ "topic": "gitlab.pipeline", "title": "acme/shop: pipeline #31 failed on main", "severity": "error", "status": "firing", "body": "https://gitlab.com/acme/shop/-/pipelines/31", "labels": { "kind": "pipeline", "project": "acme/shop", "ref": "main", "user": "kris" }, "dedup_key": "acme/shop/pipeline/main", "payload": { "object_kind": "pipeline", "…": "…" }}You can send it by hand with the secret token to try a route:
curl -X POST http://localhost:7269/v1/in/<source id> \ -H 'Content-Type: application/json' \ -H 'X-Gitlab-Token: <the source key>' \ -d '{"object_kind":"pipeline","project":{"path_with_namespace":"acme/shop"},"object_attributes":{"id":31,"ref":"main","status":"failed"}}'What resolves what
Section titled “What resolves what”Only pipelines resolve anything. A pipeline on the same ref of the same project whose status is success is a resolved event with the same dedup key as an earlier failed pipeline, so it closes the notifications that failure opened. See Notifications.
Before you write a route, note that every pipeline request that is neither failed nor success, for example pending, running or canceled, is a firing event with severity info and the same dedup key. To be told only about failures, give the route a minimum severity of error. See Routes.
The dedup key has no pipeline id in it: all pipelines on one ref of one project share it. Merge requests, pushes and other events have no dedup key and are always firing.
Repeated requests
Section titled “Repeated requests”When a request carries an Idempotency-Key or webhook-id header, ramzes uses its value as the idempotency key. A second request with the same value stores nothing and is answered 202 with the first request’s ids and "duplicate": true.
Answers and limits
Section titled “Answers and limits”The body may be at most 1 MiB, and the source may send 6,000 events per minute; past that the answer is 429 with a Retry-After header. All answers are listed in Source mappings.