Real-time GitLab CI/CD pipeline notifications delivered to Telegram.
English |
Russian
- Project Overview
- Features
- Tech Stack
- Project Structure
- Getting Started
- Configuration
- GitLab Webhook Setup
- Deployment
- Usage
- Testing
- Development
- License
pipebot is a lightweight, production-ready webhook relay that bridges GitLab CI/CD pipeline events to Telegram group chats. It receives pipeline webhooks from GitLab, formats them into readable notifications, and sends them to designated Telegram chats in real time. The bot supports multi-repository mode, allowing you to monitor multiple GitLab projects and route alerts to different Telegram groups.
- Multi-repository support - Monitor many GitLab projects and route alerts to different Telegram chats
- Stage-aware notifications - Per-job alerts with stage name headers and configurable filtering via
notifyRules - Job-level webhooks - Receives individual job events from GitLab for real-time per-stage notifications without state tracking
- Customizable alerts - Per-repo display names, three message styles (card, tree, minimal), and custom deploy link buttons
- Inline keyboard - Quick-access buttons for pipeline, commit, and repository links in every notification
- Secure & production-ready - Timing-safe webhook validation, rate limiting, security headers, payload size limits, structured logging with sensitive data sanitization
- Flexible deployment - Configurable port with automatic fallback, bot runs via long polling alongside the Express webhook server
- Diagnostics endpoint - Health check endpoint that reports configuration status without exposing secrets
- Zero-downtime startup - Bot initializes independently from the webhook server; missing credentials do not block server startup
| Technology | Purpose |
|---|---|
| Node.js >= 18 | JavaScript runtime |
| Express 5 | Webhook HTTP server |
| Telegraf | Telegram Bot API client |
| dotenv | Environment variable loading |
pipebot/
├── src/
│ ├── index.js # Entry point: config, initialization, multi-port startup
│ ├── bot/
│ │ └── index.js # Telegraf bot creation and message dispatch
│ ├── server/
│ │ └── index.js # Express server: routes, webhook validation, payload routing, rate limiting
│ ├── services/
│ │ ├── gitlab.js # GitLab payload formatting entry point
│ │ └── message-builder.js # Message templates (card, tree, minimal) and HTML escaping
│ └── utils/
│ ├── logger.js # Structured JSON logging (INFO/ERROR) with sensitive data sanitization
│ ├── repo-config.js # Multi-repository config parser, routing, and notification filtering
│ └── pipeline-state.js # Stage transition detection (legacy pipeline webhook support)
├── config/
│ └── repos.config.js # Readable repository configuration (not committed)
├── scripts/
│ └── build-config.js # CLI tool to generate env-ready REPOSITORY_CONFIG
├── test/
│ ├── fixtures/ # Sample GitLab webhook payloads
│ │ ├── pipeline-running.json
│ │ ├── pipeline-success.json
│ │ ├── pipeline-failed.json
│ │ ├── pipeline-canceled.json
│ │ ├── job-running.json
│ │ ├── job-success.json
│ │ ├── job-failed.json
│ │ └── job-canceled.json
│ ├── test-unit.js # Unit tests for message formatting
│ ├── test-repo-config.js # Multi-repository config tests
│ ├── test-pipeline-state.js # Pipeline state tracking tests
│ ├── test-webhooks.js # Webhook integration tests
│ ├── test-security.js # Security validation tests
│ ├── test-telegram.js # Telegram bot tests
│ └── test-visual.js # Visual output tests
├── index.js # Root entry point (re-exports src/index.js)
├── package.json # Dependencies and npm scripts
└── .env # Environment variables (not committed)
- Node.js >= 18
- npm (bundled with Node.js)
- A Telegram bot token from @BotFather
- One or more Telegram group/channel IDs for notifications
- Access to GitLab project webhook settings
-
Clone the repository:
git clone https://github.com/your-org/pipebot.git cd pipebot -
Install dependencies:
npm install
-
Create a
.envfile:cp .env.example .env # or create .env manually
-
Fill in your
.envfile with the required credentials (see Configuration). -
Start the application:
npm start
-
Configure GitLab webhooks pointing to your server URL (see GitLab Webhook Setup).
-
Trigger a pipeline in GitLab and watch the notification appear in Telegram.
Create a .env file in the project root with the following variables:
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | -- | Bot token obtained from @BotFather |
REPOSITORY_CONFIG |
Yes | -- | JSON array mapping GitLab projects to Telegram chats (see below) |
ALERT_STYLE |
No | card |
Global fallback message format: card, tree, or minimal |
PORT |
No | 3000 |
Primary port for the webhook server |
WEBHOOK_MODE |
No | both |
Event type to process: both, jobs (job events only), or pipeline (pipeline events only) |
Important
Never commit your .env file. It is listed in .gitignore and should only exist locally or in your deployment platform's secret store.
The REPOSITORY_CONFIG variable defines which GitLab projects to monitor and where to send their alerts. It is a JSON array of repository entries:
[
{
"projectId": 123,
"projectName": "My Awesome API",
"chatId": "-1001234567890",
"secret": "webhook-secret-for-repo-1",
"style": "card"
},
{
"projectId": 456,
"projectName": "Frontend App",
"chatId": "-1009876543210",
"secret": "webhook-secret-for-repo-2",
"style": "tree"
}
]Fields per repository entry:
| Field | Required | Description |
|---|---|---|
projectId |
Yes | GitLab project ID (numeric, found in Settings > General) |
projectName |
No | Custom display name for alerts (overrides GitLab project name) |
chatId |
Yes | Telegram chat/group ID to send alerts to |
secret |
No | Webhook secret token (must match GitLab webhook settings) |
style |
No | Alert style: card, tree, or minimal (falls back to ALERT_STYLE) |
notifyRules |
No | Per-stage notification filtering rules (see Notification Rules) |
deployLinks |
No | Per-stage custom action buttons for notifications (see Deploy Links) |
In your .env file, the JSON must be on a single line:
REPOSITORY_CONFIG=[{"projectId":123,"projectName":"My API","chatId":"-1001234567890","secret":"secret1","style":"card"},{"projectId":456,"projectName":"Frontend","chatId":"-1009876543210","secret":"secret2","style":"tree"}]The notifyRules field lets you control which pipeline statuses trigger notifications for each stage. This is useful for reducing noise -- for example, only getting alerts for failed tests or successful deployments.
{
"projectId": 123,
"projectName": "My API",
"chatId": "-1001234567890",
"notifyRules": {
"build": { "send": ["success", "failed", "running"], "ignore": ["canceled", "pending", "manual"] },
"deploy": { "send": ["success", "failed"], "ignore": [] },
"test": { "send": ["failed"], "ignore": [] }
}
}How it works:
sendis a whitelist -- only these statuses trigger a notification for that stage.ignoreis a blacklist -- these statuses are skipped even if they would otherwise be sent.- If a stage is not listed in
notifyRules, all its events are sent (backward compatible). - If
notifyRulesis not present at all, all events are sent (default behavior). sendtakes priority: if a status is in bothsendandignore, it is sent.- Valid statuses:
success,failed,running,canceled,pending,manual. - The stage name is extracted from the GitLab payload (
builds[].stage,detailed_status.context, orstages). If the stage cannot be determined, it is treated as"unknown"inrepo-config.jsor"pipeline"inmessage-builder.js-- if"unknown"is not innotifyRules, the event is sent.
The deployLinks field adds a custom full-width button at the bottom of the notification keyboard. You can configure per-stage links, optionally filtered by branch.
Branch-aware format (recommended): Provide an array of rules, each with a branch, url, and name. The first matching branch wins:
{
"projectId": 123,
"projectName": "My API",
"chatId": "-1001234567890",
"deployLinks": {
"deploy": [
{ "branch": "main", "url": "https://my-api.example.com", "name": "Production" },
{ "branch": "develop", "url": "https://dev.my-api.example.com", "name": "Development" },
{ "branch": "staging", "url": "https://staging.my-api.example.com", "name": "Staging" }
]
}
}Simple format (backward compatible): A single { url, name } object — shown for any branch:
{
"deployLinks": {
"deploy": { "url": "https://my-api.example.com", "name": "Open Site" }
}
}- Staged without a deploy link entry (e.g.
build,test) never get a button. - In the array format, if no rule's
branchmatches the pipeline branch, no button is shown. - The button appears on its own row at the very bottom, after the Pipeline/Commit buttons.
- If
deployLinksis not configured for the repo or the current stage, behavior is unchanged.
The style field (per repo) or ALERT_STYLE (global fallback) controls how pipeline notifications appear in Telegram:
| Style | Description |
|---|---|
card |
Clean card layout with bold labels and monospaced values. |
tree |
Structured list with tree-style connectors (├─ / └─). |
minimal |
Compact format with inline badges separated by label. |
All styles include an inline keyboard with buttons linking to the pipeline, commit, and repository. When deployLinks is configured, an additional custom button appears at the bottom.
Stage-aware headers: All notification styles include the pipeline stage name in the header line:
🔄 Running [build]
❌ Failed [deploy]
✅ Passed [test]
The stage name is extracted from the GitLab payload (builds[].stage, detailed_status.context, or stages). If none are available, it falls back to [pipeline].
Instead of writing inline JSON in .env, you can use the config file tool for a cleaner workflow:
-
Edit
config/repos.config.js-- a readable JS object format for all your repositories. -
Generate the env-ready JSON:
# Print JSON to stdout npm run config:build # Print in REPOSITORY_CONFIG= format npm run config:build -- --env # Write directly to .env npm run config:write
Configure a webhook for each repository you want to monitor:
-
Open your GitLab project and navigate to Settings > Webhooks.
-
Set the URL to your deployed endpoint:
https://your-domain.com/api/webhook/gitlab -
Set the Secret token to the
secretvalue from the corresponding entry inREPOSITORY_CONFIG. -
Under Trigger, check Job events (recommended) or Pipeline events (legacy support).
-
Keep Enable SSL verification enabled for production deployments.
-
Click Add webhook and test using Test > Job events (or Pipeline events if using legacy mode).
Note
All repositories use the same webhook endpoint. The bot routes alerts to the correct Telegram chat based on the projectId in the incoming payload.
Job events (recommended): Each job status change triggers a separate webhook. Notifications are sent per-job with the stage name in the header. No state tracking needed.
Pipeline events (legacy): The bot still supports pipeline webhooks for backward compatibility. Stage transitions are detected from the builds[] array.
The bot processes events with statuses: running, success, failed, and canceled. Other events are acknowledged but ignored.
The application can be deployed to any platform that supports Node.js (Railway, Render, Fly.io, Docker, VPS, etc.):
-
Set the required environment variables in your platform's configuration:
TELEGRAM_BOT_TOKEN-- your bot tokenREPOSITORY_CONFIG-- single-line JSON array of repository mappingsALERT_STYLE-- optional global fallback style (default:card)PORT-- the port your platform assigns (default:3000)
-
Set the start command to:
node src/index.js
-
Deploy. The server will bind to
0.0.0.0on the configured port.
Note
Make sure your deployment platform exposes the webhook endpoint publicly over HTTPS. GitLab requires SSL verification for webhooks in production.
npm startExpected output:
{"level":"INFO","timestamp":"2026-05-22T12:00:00.000Z","message":"Telegram bot initialized successfully","context":{}}
{"level":"INFO","timestamp":"2026-05-22T12:00:00.000Z","message":"Loaded 2 repository configuration(s)","context":{}}
{"level":"INFO","timestamp":"2026-05-22T12:00:00.000Z","message":"Starting webhook server...","context":{}}
{"level":"INFO","timestamp":"2026-05-22T12:00:00.000Z","message":"Webhook server listening on 0.0.0.0:3000","context":{}}- URL:
POST /api/webhook/gitlab - Headers:
X-Gitlab-Token: <repo-secret> - Body: GitLab job event or pipeline event payload (JSON, max 50kb)
- Response:
200 OKon success,400 Bad Requestif payload is invalid JSON,401 Unauthorizedif the secret does not match,404if no repository config matches the project,413 Payload Too Largeif payload exceeds 50kb,429 Too Many Requestsif rate limit exceeded,500 Internal Server Erroron processing error,503 Service Unavailableif bot is not initialized
- URL:
GET / - Response: JSON object with server status and timestamp
{
"status": "running",
"timestamp": "2026-05-26T20:53:57.063Z"
}Run the full test suite:
npm testThis executes five test suites in sequence:
| Script | Description |
|---|---|
npm run test:unit |
Unit tests for message formatting, duration parsing, and stage formatting |
npm run test:repo-config |
Multi-repository config parsing and routing tests |
npm run test:pipeline-state |
Pipeline state tracking and stage transition detection tests |
npm run test:webhooks |
Webhook integration tests with sample payloads |
npm run test:security |
Security validation tests (token verification, input sanitization) |
Tests use fixture files in test/fixtures/ that simulate real GitLab job and pipeline event payloads.
| Command | Description |
|---|---|
npm start |
Start the application |
npm test |
Run all tests |
npm run test:unit |
Run unit tests only |
npm run test:repo-config |
Run repository config tests only |
npm run test:pipeline-state |
Run pipeline state tests only |
npm run test:webhooks |
Run webhook tests only |
npm run test:security |
Run security tests only |
npm run test:telegram |
Run Telegram bot tests only |
npm run config:build |
Print env-ready REPOSITORY_CONFIG JSON to stdout |
npm run config:write |
Write REPOSITORY_CONFIG to .env |
- Open
src/services/message-builder.js. - Create a new formatter function (e.g.,
formatCompactStyle(data)). - Add a case in the
buildMessage()switch statement. - Add corresponding tests in
test/test-unit.jsusing an existing or new fixture. - Run
npm testto verify.
The application uses structured JSON logging via src/utils/logger.js. All log entries include a level, timestamp, message, and optional context. Errors additionally include error and stack fields. Sensitive data (tokens, secrets, passwords) is automatically sanitized in logs. Logs are written to stdout (INFO) and stderr (ERROR) for easy integration with log aggregators.
This project is licensed under the ISC License.
Built with ❤ by nightrunner91