774 lines
20 KiB
Markdown
774 lines
20 KiB
Markdown
# GitLab Webhooks Reference
|
|
|
|
## Overview
|
|
|
|
GitLab webhooks allow external services to be notified when certain events happen in GitLab. When triggered, GitLab sends an HTTP POST request with event data to the configured URL.
|
|
|
|
## Webhook Configuration
|
|
|
|
### Creating a Webhook
|
|
|
|
**Via UI**: Project Settings > Webhooks
|
|
|
|
**Via API**:
|
|
```
|
|
POST /projects/:id/hooks
|
|
```
|
|
|
|
Parameters:
|
|
- `url` (required): The webhook URL
|
|
- `token`: Secret token for request verification
|
|
- `push_events`: Trigger on push events (default: true)
|
|
- `tag_push_events`: Trigger on tag push events
|
|
- `issues_events`: Trigger on issue events
|
|
- `confidential_issues_events`: Trigger on confidential issue events
|
|
- `merge_requests_events`: Trigger on merge request events
|
|
- `wiki_page_events`: Trigger on wiki page events
|
|
- `deployment_events`: Trigger on deployment events
|
|
- `job_events`: Trigger on job events
|
|
- `pipeline_events`: Trigger on pipeline events
|
|
- `releases_events`: Trigger on release events
|
|
- `enable_ssl_verification`: Verify SSL certificates (default: true)
|
|
|
|
### Webhook Security
|
|
|
|
**Secret Token**: Include a secret token to verify requests:
|
|
|
|
```python
|
|
import hmac
|
|
import hashlib
|
|
|
|
def verify_webhook(payload, signature, secret):
|
|
expected = hmac.new(
|
|
secret.encode(),
|
|
payload.encode(),
|
|
hashlib.sha256
|
|
).hexdigest()
|
|
return hmac.compare_digest(expected, signature)
|
|
```
|
|
|
|
**Request Headers**:
|
|
- `X-Gitlab-Token`: Contains the secret token (if configured)
|
|
- `X-Gitlab-Event`: Event type name
|
|
- `User-Agent`: GitLab/{version}
|
|
|
|
## Event Types
|
|
|
|
### Push Events
|
|
|
|
**Trigger**: Code pushed to repository
|
|
|
|
**Event Header**: `X-Gitlab-Event: Push Hook`
|
|
|
|
**Payload Structure**:
|
|
```json
|
|
{
|
|
"object_kind": "push",
|
|
"event_name": "push",
|
|
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
|
|
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
|
|
"ref": "refs/heads/main",
|
|
"checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
|
|
"user_id": 4,
|
|
"user_name": "John Doe",
|
|
"user_username": "jdoe",
|
|
"user_email": "john@example.com",
|
|
"user_avatar": "https://gitlab.com/uploads/user/avatar/4/avatar.jpg",
|
|
"project_id": 15,
|
|
"project": {
|
|
"id": 15,
|
|
"name": "My Project",
|
|
"description": "Project description",
|
|
"web_url": "https://gitlab.com/namespace/project",
|
|
"avatar_url": null,
|
|
"git_ssh_url": "git@gitlab.com:namespace/project.git",
|
|
"git_http_url": "https://gitlab.com/namespace/project.git",
|
|
"namespace": "Namespace",
|
|
"visibility_level": 0,
|
|
"path_with_namespace": "namespace/project",
|
|
"default_branch": "main"
|
|
},
|
|
"commits": [
|
|
{
|
|
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
|
|
"message": "Fix bug in authentication",
|
|
"title": "Fix bug in authentication",
|
|
"timestamp": "2025-01-15T12:30:00+00:00",
|
|
"url": "https://gitlab.com/namespace/project/-/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
|
|
"author": {
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
},
|
|
"added": ["new-file.txt"],
|
|
"modified": ["existing-file.txt"],
|
|
"removed": ["old-file.txt"]
|
|
}
|
|
],
|
|
"total_commits_count": 1,
|
|
"repository": {
|
|
"name": "My Project",
|
|
"url": "git@gitlab.com:namespace/project.git",
|
|
"description": "Project description",
|
|
"homepage": "https://gitlab.com/namespace/project"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Tag Push Events
|
|
|
|
**Trigger**: Tag created or deleted
|
|
|
|
**Event Header**: `X-Gitlab-Event: Tag Push Hook`
|
|
|
|
**Payload Structure**:
|
|
```json
|
|
{
|
|
"object_kind": "tag_push",
|
|
"event_name": "tag_push",
|
|
"before": "0000000000000000000000000000000000000000",
|
|
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
|
|
"ref": "refs/tags/v1.0.0",
|
|
"checkout_sha": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
|
|
"user_id": 4,
|
|
"user_name": "John Doe",
|
|
"user_avatar": "https://gitlab.com/uploads/user/avatar/4/avatar.jpg",
|
|
"project_id": 15,
|
|
"project": { /* project details */ },
|
|
"commits": [ /* commit details */ ],
|
|
"total_commits_count": 0,
|
|
"repository": { /* repository details */ }
|
|
}
|
|
```
|
|
|
|
**Note**: `before` is all zeros for tag creation, `after` is all zeros for tag deletion.
|
|
|
|
### Issue Events
|
|
|
|
**Trigger**: Issue created, updated, closed, or reopened
|
|
|
|
**Event Header**: `X-Gitlab-Event: Issue Hook`
|
|
|
|
**Payload Structure**:
|
|
```json
|
|
{
|
|
"object_kind": "issue",
|
|
"event_type": "issue",
|
|
"user": {
|
|
"id": 4,
|
|
"name": "John Doe",
|
|
"username": "jdoe",
|
|
"avatar_url": "https://gitlab.com/uploads/user/avatar/4/avatar.jpg",
|
|
"email": "john@example.com"
|
|
},
|
|
"project": { /* project details */ },
|
|
"object_attributes": {
|
|
"id": 301,
|
|
"title": "Bug in login feature",
|
|
"assignee_ids": [5, 6],
|
|
"assignee_id": 5,
|
|
"author_id": 4,
|
|
"project_id": 15,
|
|
"created_at": "2025-01-15 12:30:00 UTC",
|
|
"updated_at": "2025-01-15 13:00:00 UTC",
|
|
"position": 0,
|
|
"branch_name": null,
|
|
"description": "Login page crashes when...",
|
|
"milestone_id": 10,
|
|
"state": "opened",
|
|
"state_id": 1,
|
|
"iid": 23,
|
|
"url": "https://gitlab.com/namespace/project/-/issues/23",
|
|
"action": "open",
|
|
"labels": [
|
|
{
|
|
"id": 100,
|
|
"title": "bug",
|
|
"color": "#FF0000",
|
|
"project_id": 15,
|
|
"created_at": "2024-01-01 00:00:00 UTC",
|
|
"updated_at": "2024-01-01 00:00:00 UTC"
|
|
}
|
|
]
|
|
},
|
|
"assignees": [
|
|
{
|
|
"id": 5,
|
|
"name": "Jane Smith",
|
|
"username": "jsmith",
|
|
"avatar_url": "https://gitlab.com/uploads/user/avatar/5/avatar.jpg"
|
|
}
|
|
],
|
|
"labels": [ /* label details */ ],
|
|
"changes": {
|
|
"updated_at": {
|
|
"previous": "2025-01-15 12:30:00 UTC",
|
|
"current": "2025-01-15 13:00:00 UTC"
|
|
},
|
|
"title": {
|
|
"previous": "Old title",
|
|
"current": "Bug in login feature"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Action Values**: `open`, `update`, `close`, `reopen`
|
|
|
|
### Merge Request Events
|
|
|
|
**Trigger**: MR created, updated, merged, or closed
|
|
|
|
**Event Header**: `X-Gitlab-Event: Merge Request Hook`
|
|
|
|
**Payload Structure**:
|
|
```json
|
|
{
|
|
"object_kind": "merge_request",
|
|
"event_type": "merge_request",
|
|
"user": { /* user details */ },
|
|
"project": { /* project details */ },
|
|
"object_attributes": {
|
|
"id": 99,
|
|
"iid": 1,
|
|
"target_branch": "main",
|
|
"source_branch": "feature-branch",
|
|
"source_project_id": 15,
|
|
"author_id": 4,
|
|
"assignee_ids": [5],
|
|
"assignee_id": 5,
|
|
"reviewer_ids": [6, 7],
|
|
"title": "Add new authentication feature",
|
|
"created_at": "2025-01-15 12:00:00 UTC",
|
|
"updated_at": "2025-01-15 14:00:00 UTC",
|
|
"milestone_id": 10,
|
|
"state": "opened",
|
|
"state_id": 1,
|
|
"merge_status": "can_be_merged",
|
|
"target_project_id": 15,
|
|
"description": "This MR adds...",
|
|
"url": "https://gitlab.com/namespace/project/-/merge_requests/1",
|
|
"source": { /* source project */ },
|
|
"target": { /* target project */ },
|
|
"last_commit": {
|
|
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
|
|
"message": "Update authentication",
|
|
"timestamp": "2025-01-15T13:30:00+00:00",
|
|
"url": "https://gitlab.com/namespace/project/-/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
|
|
"author": {
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
},
|
|
"work_in_progress": false,
|
|
"draft": false,
|
|
"action": "open",
|
|
"assignee": { /* assignee details */ },
|
|
"labels": [ /* label details */ ]
|
|
},
|
|
"labels": [ /* label details */ ],
|
|
"changes": { /* changed attributes */ },
|
|
"assignees": [ /* assignee details */ ],
|
|
"reviewers": [ /* reviewer details */ ]
|
|
}
|
|
```
|
|
|
|
**Action Values**: `open`, `update`, `close`, `reopen`, `merge`, `approved`, `unapproved`
|
|
|
|
### Pipeline Events
|
|
|
|
**Trigger**: Pipeline status changes
|
|
|
|
**Event Header**: `X-Gitlab-Event: Pipeline Hook`
|
|
|
|
**Payload Structure**:
|
|
```json
|
|
{
|
|
"object_kind": "pipeline",
|
|
"object_attributes": {
|
|
"id": 31,
|
|
"ref": "main",
|
|
"tag": false,
|
|
"sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
|
|
"before_sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
|
|
"source": "push",
|
|
"status": "success",
|
|
"detailed_status": "passed",
|
|
"stages": ["build", "test", "deploy"],
|
|
"created_at": "2025-01-15 12:00:00 UTC",
|
|
"finished_at": "2025-01-15 12:15:00 UTC",
|
|
"duration": 900,
|
|
"queued_duration": 10,
|
|
"variables": [
|
|
{
|
|
"key": "ENVIRONMENT",
|
|
"value": "production"
|
|
}
|
|
]
|
|
},
|
|
"merge_request": {
|
|
"id": 1,
|
|
"iid": 1,
|
|
"title": "Add feature",
|
|
"source_branch": "feature-branch",
|
|
"source_project_id": 15,
|
|
"target_branch": "main",
|
|
"target_project_id": 15,
|
|
"state": "opened",
|
|
"merge_status": "can_be_merged",
|
|
"url": "https://gitlab.com/namespace/project/-/merge_requests/1"
|
|
},
|
|
"user": { /* user details */ },
|
|
"project": { /* project details */ },
|
|
"commit": {
|
|
"id": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
|
|
"message": "Add new feature",
|
|
"title": "Add new feature",
|
|
"timestamp": "2025-01-15T11:55:00+00:00",
|
|
"url": "https://gitlab.com/namespace/project/-/commit/bcbb5ec396a2c0f828686f14fac9b80b780504f2",
|
|
"author": {
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
},
|
|
"builds": [
|
|
{
|
|
"id": 380,
|
|
"stage": "test",
|
|
"name": "unit-tests",
|
|
"status": "success",
|
|
"created_at": "2025-01-15 12:00:00 UTC",
|
|
"started_at": "2025-01-15 12:01:00 UTC",
|
|
"finished_at": "2025-01-15 12:05:00 UTC",
|
|
"duration": 240,
|
|
"queued_duration": 60,
|
|
"when": "on_success",
|
|
"manual": false,
|
|
"allow_failure": false,
|
|
"user": { /* user details */ },
|
|
"runner": {
|
|
"id": 1,
|
|
"description": "runner-1",
|
|
"active": true,
|
|
"runner_type": "project_type",
|
|
"is_shared": false,
|
|
"tags": ["docker", "linux"]
|
|
},
|
|
"artifacts_file": {
|
|
"filename": "artifacts.zip",
|
|
"size": 1024000
|
|
},
|
|
"environment": null
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Status Values**: `pending`, `running`, `success`, `failed`, `canceled`, `skipped`
|
|
|
|
### Job Events
|
|
|
|
**Trigger**: Job status changes
|
|
|
|
**Event Header**: `X-Gitlab-Event: Job Hook`
|
|
|
|
**Payload Structure**:
|
|
```json
|
|
{
|
|
"object_kind": "build",
|
|
"ref": "main",
|
|
"tag": false,
|
|
"before_sha": "95790bf891e76fee5e1747ab589903a6a1f80f22",
|
|
"sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
|
|
"build_id": 380,
|
|
"build_name": "unit-tests",
|
|
"build_stage": "test",
|
|
"build_status": "success",
|
|
"build_created_at": "2025-01-15 12:00:00 UTC",
|
|
"build_started_at": "2025-01-15 12:01:00 UTC",
|
|
"build_finished_at": "2025-01-15 12:05:00 UTC",
|
|
"build_duration": 240,
|
|
"build_queued_duration": 60,
|
|
"build_allow_failure": false,
|
|
"build_failure_reason": "unknown_failure",
|
|
"pipeline_id": 31,
|
|
"runner": {
|
|
"id": 1,
|
|
"description": "runner-1",
|
|
"runner_type": "project_type",
|
|
"active": true,
|
|
"is_shared": false,
|
|
"tags": ["docker", "linux"]
|
|
},
|
|
"project_id": 15,
|
|
"project_name": "My Project",
|
|
"user": { /* user details */ },
|
|
"commit": { /* commit details */ },
|
|
"repository": { /* repository details */ },
|
|
"environment": {
|
|
"name": "production",
|
|
"action": "start",
|
|
"deployment_tier": "production"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Build Status Values**: `pending`, `running`, `success`, `failed`, `canceled`
|
|
|
|
### Deployment Events
|
|
|
|
**Trigger**: Deployment created or status changes
|
|
|
|
**Event Header**: `X-Gitlab-Event: Deployment Hook`
|
|
|
|
**Payload Structure**:
|
|
```json
|
|
{
|
|
"object_kind": "deployment",
|
|
"status": "success",
|
|
"status_changed_at": "2025-01-15 12:20:00 UTC",
|
|
"deployment_id": 15,
|
|
"deployable_id": 380,
|
|
"deployable_url": "https://gitlab.com/namespace/project/-/jobs/380",
|
|
"environment": "production",
|
|
"environment_tier": "production",
|
|
"environment_slug": "production",
|
|
"environment_external_url": "https://prod.example.com",
|
|
"project": { /* project details */ },
|
|
"short_sha": "da156088",
|
|
"user": { /* user details */ },
|
|
"user_url": "https://gitlab.com/jdoe",
|
|
"commit_url": "https://gitlab.com/namespace/project/-/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
|
|
"commit_title": "Deploy to production"
|
|
}
|
|
```
|
|
|
|
**Status Values**: `created`, `running`, `success`, `failed`, `canceled`
|
|
|
|
### Wiki Page Events
|
|
|
|
**Trigger**: Wiki page created, updated, or deleted
|
|
|
|
**Event Header**: `X-Gitlab-Event: Wiki Page Hook`
|
|
|
|
**Payload Structure**:
|
|
```json
|
|
{
|
|
"object_kind": "wiki_page",
|
|
"user": { /* user details */ },
|
|
"project": { /* project details */ },
|
|
"wiki": {
|
|
"web_url": "https://gitlab.com/namespace/project/-/wikis/home",
|
|
"git_ssh_url": "git@gitlab.com:namespace/project.wiki.git",
|
|
"git_http_url": "https://gitlab.com/namespace/project.wiki.git",
|
|
"path_with_namespace": "namespace/project",
|
|
"default_branch": "main"
|
|
},
|
|
"object_attributes": {
|
|
"title": "Home",
|
|
"content": "# Welcome\n\nThis is the home page...",
|
|
"format": "markdown",
|
|
"message": "Update home page",
|
|
"slug": "home",
|
|
"url": "https://gitlab.com/namespace/project/-/wikis/home",
|
|
"action": "update"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Action Values**: `create`, `update`, `delete`
|
|
|
|
### Release Events
|
|
|
|
**Trigger**: Release created, updated, or deleted
|
|
|
|
**Event Header**: `X-Gitlab-Event: Release Hook`
|
|
|
|
**Payload Structure**:
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"created_at": "2025-01-15 12:00:00 UTC",
|
|
"description": "Release notes for v1.0.0",
|
|
"name": "Version 1.0.0",
|
|
"released_at": "2025-01-15 12:00:00 UTC",
|
|
"tag": "v1.0.0",
|
|
"object_kind": "release",
|
|
"project": { /* project details */ },
|
|
"url": "https://gitlab.com/namespace/project/-/releases/v1.0.0",
|
|
"action": "create",
|
|
"assets": {
|
|
"count": 2,
|
|
"links": [
|
|
{
|
|
"id": 1,
|
|
"external": true,
|
|
"link_type": "other",
|
|
"name": "Binary",
|
|
"url": "https://example.com/binary"
|
|
}
|
|
],
|
|
"sources": [
|
|
{
|
|
"format": "zip",
|
|
"url": "https://gitlab.com/namespace/project/-/archive/v1.0.0/project-v1.0.0.zip"
|
|
},
|
|
{
|
|
"format": "tar.gz",
|
|
"url": "https://gitlab.com/namespace/project/-/archive/v1.0.0/project-v1.0.0.tar.gz"
|
|
}
|
|
]
|
|
},
|
|
"commit": { /* commit details */ }
|
|
}
|
|
```
|
|
|
|
**Action Values**: `create`, `update`, `delete`
|
|
|
|
## Testing Webhooks
|
|
|
|
### Via GitLab UI
|
|
|
|
1. Navigate to Project Settings > Webhooks
|
|
2. Find your webhook
|
|
3. Click "Test" dropdown
|
|
4. Select event type to test
|
|
5. View response in UI
|
|
|
|
### Via API
|
|
|
|
```bash
|
|
curl -X POST "https://gitlab.com/api/v4/projects/:id/hooks/:hook_id/test/push_events" \
|
|
--header "PRIVATE-TOKEN: <token>"
|
|
```
|
|
|
|
### Local Testing
|
|
|
|
Use tools like:
|
|
- **ngrok**: Create public URL for local development
|
|
- **webhook.site**: Test webhook delivery
|
|
- **requestbin.com**: Inspect webhook payloads
|
|
|
|
```bash
|
|
# Using ngrok
|
|
ngrok http 3000
|
|
|
|
# Update webhook URL to ngrok URL
|
|
# Trigger event in GitLab
|
|
# View webhook payload in ngrok dashboard
|
|
```
|
|
|
|
## Webhook Best Practices
|
|
|
|
### 1. Security
|
|
|
|
- **Use HTTPS**: Always use HTTPS URLs for webhooks
|
|
- **Verify SSL**: Enable SSL verification in production
|
|
- **Secret Tokens**: Use secret tokens to verify webhook authenticity
|
|
- **Validate Payloads**: Verify request signatures
|
|
- **IP Allowlisting**: Restrict webhook sources to GitLab IPs
|
|
|
|
### 2. Reliability
|
|
|
|
- **Return Quickly**: Respond with 2xx status code quickly (< 10 seconds)
|
|
- **Async Processing**: Queue webhook payloads for async processing
|
|
- **Idempotency**: Handle duplicate webhook deliveries
|
|
- **Error Handling**: Handle errors gracefully
|
|
- **Retry Logic**: Implement retry logic for failed processing
|
|
|
|
### 3. Monitoring
|
|
|
|
- **Log Webhooks**: Log all webhook deliveries
|
|
- **Track Failures**: Monitor webhook failure rates
|
|
- **Alert on Issues**: Set up alerts for webhook failures
|
|
- **Recent Deliveries**: Check recent deliveries in GitLab UI
|
|
|
|
### 4. Performance
|
|
|
|
- **Rate Limiting**: Handle rate limits on external APIs
|
|
- **Batch Processing**: Batch similar webhook events
|
|
- **Caching**: Cache frequently accessed data
|
|
- **Timeouts**: Set appropriate timeouts
|
|
|
|
## Example Webhook Handler
|
|
|
|
### Python Flask Example
|
|
|
|
```python
|
|
from flask import Flask, request, jsonify
|
|
import hmac
|
|
import hashlib
|
|
|
|
app = Flask(__name__)
|
|
WEBHOOK_SECRET = "your-secret-token"
|
|
|
|
def verify_signature(payload, signature):
|
|
"""Verify webhook signature"""
|
|
if not signature:
|
|
return False
|
|
|
|
expected = hmac.new(
|
|
WEBHOOK_SECRET.encode(),
|
|
payload,
|
|
hashlib.sha256
|
|
).hexdigest()
|
|
|
|
return hmac.compare_digest(expected, signature)
|
|
|
|
@app.route('/webhook', methods=['POST'])
|
|
def handle_webhook():
|
|
# Get signature from header
|
|
signature = request.headers.get('X-Gitlab-Token')
|
|
|
|
# Verify signature
|
|
if not verify_signature(request.data, signature):
|
|
return jsonify({'error': 'Invalid signature'}), 401
|
|
|
|
# Get event type
|
|
event_type = request.headers.get('X-Gitlab-Event')
|
|
|
|
# Parse payload
|
|
payload = request.json
|
|
|
|
# Handle different event types
|
|
if event_type == 'Push Hook':
|
|
handle_push_event(payload)
|
|
elif event_type == 'Merge Request Hook':
|
|
handle_merge_request_event(payload)
|
|
elif event_type == 'Pipeline Hook':
|
|
handle_pipeline_event(payload)
|
|
|
|
return jsonify({'status': 'received'}), 200
|
|
|
|
def handle_push_event(payload):
|
|
"""Handle push events"""
|
|
ref = payload['ref']
|
|
commits = payload['commits']
|
|
print(f"Received push to {ref} with {len(commits)} commits")
|
|
|
|
def handle_merge_request_event(payload):
|
|
"""Handle merge request events"""
|
|
action = payload['object_attributes']['action']
|
|
iid = payload['object_attributes']['iid']
|
|
print(f"Merge request {iid} was {action}")
|
|
|
|
def handle_pipeline_event(payload):
|
|
"""Handle pipeline events"""
|
|
status = payload['object_attributes']['status']
|
|
ref = payload['object_attributes']['ref']
|
|
print(f"Pipeline on {ref} status: {status}")
|
|
|
|
if __name__ == '__main__':
|
|
app.run(port=5000)
|
|
```
|
|
|
|
### Node.js Express Example
|
|
|
|
```javascript
|
|
const express = require('express');
|
|
const crypto = require('crypto');
|
|
const bodyParser = require('body-parser');
|
|
|
|
const app = express();
|
|
const WEBHOOK_SECRET = 'your-secret-token';
|
|
|
|
// Verify webhook signature
|
|
function verifySignature(payload, signature) {
|
|
if (!signature) return false;
|
|
|
|
const expected = crypto
|
|
.createHmac('sha256', WEBHOOK_SECRET)
|
|
.update(payload)
|
|
.digest('hex');
|
|
|
|
return crypto.timingSafeEqual(
|
|
Buffer.from(expected),
|
|
Buffer.from(signature)
|
|
);
|
|
}
|
|
|
|
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (req, res) => {
|
|
const signature = req.headers['x-gitlab-token'];
|
|
const eventType = req.headers['x-gitlab-event'];
|
|
|
|
// Verify signature
|
|
if (!verifySignature(req.body, signature)) {
|
|
return res.status(401).json({ error: 'Invalid signature' });
|
|
}
|
|
|
|
const payload = JSON.parse(req.body.toString());
|
|
|
|
// Handle different event types
|
|
switch (eventType) {
|
|
case 'Push Hook':
|
|
handlePushEvent(payload);
|
|
break;
|
|
case 'Merge Request Hook':
|
|
handleMergeRequestEvent(payload);
|
|
break;
|
|
case 'Pipeline Hook':
|
|
handlePipelineEvent(payload);
|
|
break;
|
|
}
|
|
|
|
res.json({ status: 'received' });
|
|
});
|
|
|
|
function handlePushEvent(payload) {
|
|
console.log(`Push to ${payload.ref} with ${payload.commits.length} commits`);
|
|
}
|
|
|
|
function handleMergeRequestEvent(payload) {
|
|
const { action, iid } = payload.object_attributes;
|
|
console.log(`Merge request ${iid} was ${action}`);
|
|
}
|
|
|
|
function handlePipelineEvent(payload) {
|
|
const { status, ref } = payload.object_attributes;
|
|
console.log(`Pipeline on ${ref} status: ${status}`);
|
|
}
|
|
|
|
app.listen(5000, () => {
|
|
console.log('Webhook server listening on port 5000');
|
|
});
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **Webhook Not Triggering**
|
|
- Verify event is enabled in webhook configuration
|
|
- Check webhook URL is accessible from internet
|
|
- Review Recent Deliveries in GitLab UI
|
|
- Check firewall rules
|
|
|
|
2. **SSL Verification Failures**
|
|
- Ensure SSL certificate is valid
|
|
- Check certificate chain is complete
|
|
- Temporarily disable SSL verification for testing (not recommended for production)
|
|
|
|
3. **Timeouts**
|
|
- Reduce processing time in webhook handler
|
|
- Return 2xx response quickly, process async
|
|
- Check network connectivity
|
|
|
|
4. **Authentication Errors**
|
|
- Verify secret token matches
|
|
- Check signature verification logic
|
|
- Ensure token is transmitted correctly
|
|
|
|
### Debugging Tips
|
|
|
|
1. Use webhook testing services (webhook.site, requestbin.com)
|
|
2. Check "Recent Deliveries" in GitLab webhook settings
|
|
3. Enable detailed logging in webhook handler
|
|
4. Test with curl/Postman using sample payloads
|
|
5. Verify IP addresses if using allowlisting
|
|
6. Check response status codes and headers
|
|
|
|
## Additional Resources
|
|
|
|
- GitLab Webhooks Documentation: https://docs.gitlab.com/ee/user/project/integrations/webhooks.html
|
|
- Webhook Events: https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html
|
|
- System Hooks: https://docs.gitlab.com/ee/administration/system_hooks.html
|