GitLab Webhooks and API
GitLab Webhooks send automatic notifications to external services when something happens in your project. The GitLab API lets external tools read and write GitLab data programmatically. Together, they connect GitLab to the rest of your technology stack.
What a Webhook Is — The Doorbell Analogy
Normal API (you ask): Webhook (GitLab tells you): ─────────────────────── ────────────────────────────────────── Your app → "Any new MRs?" GitLab → POST request to your app GitLab → "Yes, here are 3" Payload: "MR #48 just merged on main" (you must keep asking) (GitLab rings your doorbell automatically)
Webhooks eliminate the need to repeatedly poll GitLab for updates. GitLab pushes data to you the moment an event occurs.
Events That Trigger Webhooks
| Event | When It Fires | Common Use |
|---|---|---|
| Push events | Someone pushes commits to any branch | Trigger an external build system |
| Merge request events | MR opened, approved, merged, or closed | Post a Slack notification to the team |
| Pipeline events | Pipeline starts, passes, or fails | Update a dashboard or send an alert |
| Issue events | Issue created, updated, or closed | Sync with an external project tracker |
| Release events | A new release is published | Notify the customer success team |
| Deployment events | A deployment starts or finishes | Update a status page automatically |
Creating a Webhook
Go to Settings → Webhooks → Add new webhook. Fill in:
URL: https://hooks.slack.com/services/T00/B00/xxxx
Secret token: (optional — used to verify the sender is GitLab)
Trigger: ✅ Merge request events
✅ Pipeline events
SSL verification: ✅ enabled (always keep this on)
→ Save → Test → GitLab sends a sample payload to your URL
Webhook Payload — What GitLab Sends
GitLab sends an HTTP POST request to your URL with a JSON body containing details about the event.
Webhook payload example (MR merged):
──────────────────────────────────────────────────────────────
{
"object_kind": "merge_request",
"event_type": "merge_request",
"user": { "name": "Sara Ahmed", "username": "sara" },
"project": { "name": "my-webapp", "id": 1234 },
"object_attributes": {
"id": 48,
"title": "Add search bar",
"state": "merged",
"target_branch": "main",
"url": "https://gitlab.com/acme/my-webapp/-/merge_requests/48"
}
}
──────────────────────────────────────────────────────────────
Securing Webhooks With a Secret Token
Anyone who discovers your webhook URL could send fake payloads to it. A secret token prevents this. Set a secret in GitLab when creating the webhook. GitLab sends that secret in every request header as X-Gitlab-Token. Your receiving server checks the header and rejects requests without the matching value.
Incoming webhook request headers: ──────────────────────────────────────────────────────────── POST /webhook HTTP/1.1 X-Gitlab-Token: my-secret-value-here ← your server checks this Content-Type: application/json ────────────────────────────────────────────────────────────
The GitLab REST API
The GitLab API lets any program read and write data in GitLab. You can create issues, trigger pipelines, list members, update files, and thousands of other actions — all with HTTP requests.
API base URL: https://gitlab.com/api/v4/ Authentication: add your access token to every request Header: PRIVATE-TOKEN: glpat-xxxxxxxxxxxxxxxxxxxx
Common API Operations
List All Projects You Have Access To
GET https://gitlab.com/api/v4/projects
curl --header "PRIVATE-TOKEN: glpat-xxx" \
"https://gitlab.com/api/v4/projects"
Create a New Issue
POST https://gitlab.com/api/v4/projects/1234/issues
curl --request POST \
--header "PRIVATE-TOKEN: glpat-xxx" \
--header "Content-Type: application/json" \
--data '{"title":"Login page crashes on mobile","labels":"bug,high-priority"}' \
"https://gitlab.com/api/v4/projects/1234/issues"
Trigger a Pipeline Manually
POST https://gitlab.com/api/v4/projects/1234/trigger/pipeline
curl --request POST \
--form "token=my-pipeline-trigger-token" \
--form "ref=main" \
"https://gitlab.com/api/v4/projects/1234/trigger/pipeline"
Get a List of Open Merge Requests
GET https://gitlab.com/api/v4/projects/1234/merge_requests?state=opened Response includes: MR id, title, author, labels, created_at, web_url
Pagination — Handling Large Result Sets
API responses are paginated — they return a limited number of results per page (20 by default). Check the response headers to navigate pages.
Response headers: ───────────────────────────────────────── X-Total: 247 X-Total-Pages: 13 X-Per-Page: 20 X-Page: 1 X-Next-Page: 2 Fetch page 2: GET /api/v4/projects?page=2&per_page=20
GraphQL API — An Alternative to REST
GitLab also offers a GraphQL API at https://gitlab.com/api/graphql. GraphQL lets you fetch exactly the fields you need in a single request instead of making multiple REST calls.
GraphQL query — get project name and all open issue titles:
──────────────────────────────────────────────────────────────────
{
project(fullPath: "acme/my-webapp") {
name
issues(state: opened) {
nodes {
title
createdAt
assignees { nodes { name } }
}
}
}
}
──────────────────────────────────────────────────────────────────
One request returns everything — no extra calls needed ✅
Pipeline Triggers — API-Driven Pipeline Starts
Create a pipeline trigger token at Settings → CI/CD → Pipeline triggers → Add trigger. External systems use this token to start a pipeline on demand — useful for scheduled nightly builds or cross-project dependencies.
Scenario: Your test management tool should trigger a regression
suite in GitLab every night at midnight.
Cron job runs at 00:00:
curl --request POST \
--form "token=TRIGGER_TOKEN" \
--form "ref=main" \
"https://gitlab.com/api/v4/projects/1234/trigger/pipeline"
GitLab starts the pipeline → runs tests → emails results ✅
Webhook Delivery Log
GitLab records every webhook delivery attempt. Go to Settings → Webhooks → Edit next to a webhook and click Recent deliveries. Each entry shows the payload sent, the HTTP response code from your server, and any error message.
Recent deliveries ──────────────────────────────────────────────────────────── ID Event Response Sent At ──────────────────────────────────────────────────────────── 10041 push 200 ✅ 10 minutes ago 10040 merge_request 200 ✅ 1 hour ago 10039 pipeline 500 ❌ 2 hours ago (server error) ──────────────────────────────────────────────────────────── [Resend] button available on failed deliveries
