GitLab webhooks don’t just send data; they actively enlist external systems into your CI/CD process.

Imagine you’ve just pushed a commit. GitLab, upon detecting this event, doesn’t just log it. It acts like a maître d’ at a fancy restaurant, discreetly sending a precisely formatted message to a pre-arranged table (your external system’s URL). This message, a JSON payload, contains all the juicy details: who pushed, what branch, the commit message, the project it belongs to, and even a link back to the specific commit in GitLab.

Let’s see this in action. Suppose we want to notify a custom Slack channel whenever a new tag is pushed to our production branch.

First, we need a webhook receiver. This could be a simple Python Flask app running on a public server.

from flask import Flask, request, jsonify
import json

app = Flask(__name__)

@app.route('/gitlab-webhook', methods=['POST'])
def gitlab_webhook():
    data = request.get_json()
    if not data:
        return jsonify({"message": "No JSON data received"}), 400

    event_type = request.headers.get('X-Gitlab-Event')

    if event_type == 'Tag Push Hook':
        tag_name = data['tag']
        project_name = data['project']['name']
        commit_sha = data['checkout_sha']
        commit_message = data['message']

        print(f"Received Tag Push: {tag_name} on {project_name}")
        print(f"Commit SHA: {commit_sha}")
        print(f"Commit Message: {commit_message}")

        # Here you'd integrate with Slack API or another system
        # For demonstration, we'll just print
        return jsonify({"message": f"Tag push {tag_name} processed"}), 200
    else:
        print(f"Received unhandled event type: {event_type}")
        return jsonify({"message": f"Event type {event_type} not handled"}), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Now, in your GitLab project, navigate to Settings > Webhooks. Click "Add webhook."

  • URL: http://your-server-ip:5000/gitlab-webhook (Replace your-server-ip with the actual IP of the server running the Flask app).
  • Secret Token: my-super-secret-token (This is optional but highly recommended for security. Your receiving app should verify this token).
  • Trigger: Select "Tag push events."

Once saved, push a new tag to your repository: git tag v1.0.0 && git push origin v1.0.0.

Your Flask app will receive a POST request with a JSON payload similar to this:

{
  "object_kind": "tag_push",
  "event_name": "tag_push_events",
  "before": "0000000000000000000000000000000000000000",
  "after": "a1b2c3d4e5f67890abcdef0123456789abcdef01",
  "ref": "refs/tags/v1.0.0",
  "checkout_sha": "a1b2c3d4e5f67890abcdef0123456789abcdef01",
  "message": "Release version 1.0.0",
  "user_id": 1,
  "user_name": "John Doe",
  "user_email": "john.doe@example.com",
  "project_id": 1,
  "project": {
    "id": 1,
    "name": "My Awesome Project",
    "description": "",
    "web_url": "http://gitlab.example.com/mygroup/my-awesome-project",
    "avatar_url": null,
    "git_ssh_url": "git@gitlab.example.com:mygroup/my-awesome-project.git",
    "git_http_url": "http://gitlab.example.com/mygroup/my-awesome-project.git",
    "namespace": "My Group",
    "visibility_level": 20,
    "path_with_namespace": "mygroup/my-awesome-project",
    "default_branch": "main",
    "ci_config_path": null
  },
  "commits": [],
  "repository": {
    "name": "My Awesome Project",
    "url": "http://gitlab.example.com/mygroup/my-awesome-project.git",
    "description": "",
    "homepage": "http://gitlab.example.com/mygroup/my-awesome-project",
    "git_url": "http://gitlab.example.com/mygroup/my-awesome-project.git",
    "ssh_url": "git@gitlab.example.com:mygroup/my-awesome-project.git",
    "visibility_level": 20
  },
  "tag": "v1.0.0",
  "tag_id": "a1b2c3d4e5f67890abcdef0123456789abcdef01",
  "created_at": "2023-10-27T10:00:00.000Z",
  "webhook": true
}

GitLab webhooks are fundamentally HTTP POST requests. This means any system capable of receiving and processing HTTP POST requests can be a target. The content of the request is always JSON, and GitLab provides a X-Gitlab-Event header that tells you exactly what kind of event triggered the webhook, allowing your receiving application to branch its logic accordingly. You can customize which events trigger a webhook (push, merge request, pipeline, etc.), giving you fine-grained control over what information is sent and when.

The most surprising thing about GitLab webhooks is how easily they can be used to create complex, event-driven workflows without writing a single line of CI/CD pipeline code in GitLab itself. The entire orchestration logic can live in your external services.

You can use webhooks to trigger deployments, update issue trackers, send notifications to various platforms, or even initiate entirely separate CI/CD pipelines in other systems. The key is understanding the structure of the JSON payload for each event type, which GitLab documents extensively. For example, a Merge Request Hook payload will contain different fields like object_attributes with details about the MR, assignees, and changes within the MR, which are absent in a Tag Push Hook.

The secret token, when used, is sent in the X-Gitlab-Token header. Your receiving application must check this header against the token configured in GitLab. If it doesn’t match, reject the request. This prevents unauthorized systems from triggering actions in your GitLab project.

Next, you’ll likely want to explore how to secure these webhooks further using SSL verification and how to handle retries when your external service is temporarily unavailable.

Want structured learning?

Take the full Gitlab course →