Overview
PRFlow delivers GitLab merge request notifications to Slack. For each merge request it posts one Slack message that updates in place as the MR progresses — CI/CD pipeline status, approvals, and merge state all appear on the original message, and GitLab review comments are synced into a Slack thread under it. The integration has three parts:- GitLab webhooks — your GitLab group or projects send merge request, comment, and pipeline events to a per-workspace PRFlow endpoint in real time.
- Read-only GitLab API access — PRFlow uses a credential with the
read_apiscope to list your projects during setup, to look up approval and discussion state when rendering notifications, and to run a polling fallback that catches anything a missed webhook would have dropped. - Slack Web API — PRFlow posts and updates messages in the channels you map, using a Slack bot token obtained via OAuth.
How it works
Webhook events consumed
PRFlow exposes a unique webhook endpoint per workspace (https://app.prflow.dev/webhooks/gitlab/<workspace-id>). It processes three
GitLab event types, identified by the X-Gitlab-Event header. Any other
event type is acknowledged with HTTP 200 and discarded, so you can safely
enable extra event toggles in GitLab without side effects.
| GitLab event | What PRFlow does |
|---|---|
Merge Request Hook | open / reopen post the MR message; update, close, and merge update it in place; approval / approved and unapproval / unapproved trigger an approval-state refresh (verified against the GitLab approvals API before the message shows “approved”). Draft MRs are skipped until they leave draft state. |
Note Hook | Comments on merge requests (noteable_type: MergeRequest) are posted as threaded replies under the MR message. Edited comments are updated in place; deleted comments are removed from the Slack thread. Notes on issues, commits, or snippets are ignored. |
Pipeline Hook | Updates the CI status shown on the MR message. Recognized statuses: success, failed, running, pending, canceled, skipped, manual. Pipeline updates stop once an MR is merged or closed. |
GitLab API calls
All data calls are read-onlyGET requests under the read_api scope. The
only POST PRFlow sends to GitLab is the standard OAuth token exchange and
refresh on /oauth/token. PRFlow never modifies anything in your GitLab
instance.
| Endpoint | Purpose |
|---|---|
GET /user | Identify the connected account during OAuth and token validation. |
GET /groups?top_level_only=true | Resolve the top-level GitLab group your workspace is scoped to during setup. |
GET /groups/:id/projects | List projects (including subgroups) when you add repositories. Only projects where the credential has at least Developer access are listed. |
GET /projects/:id, GET /groups/:id | Fetch project and group metadata (name, path, avatar) for the dashboard. |
GET /projects/:id/merge_requests/:iid/approvals | Check approvals remaining, so the message can show an accurate “needs approvals” indicator and only flips to “approved” when approval rules are actually satisfied. |
GET /projects/:id/merge_requests/:iid/discussions | Count unresolved discussions for the “has unresolved discussions” indicator. |
GET /projects/:id/merge_requests, GET .../merge_requests/:iid/notes | Background polling fallback: PRFlow periodically diffs merge request state via the API and emits the same message updates if a webhook delivery was missed. |
GET /personal_access_tokens/self | Validate a personal access token when you save one: scope check, expiry, and revocation status. |
POST /oauth/token | OAuth authorization-code exchange and automatic refresh-token rotation (gitlab.com OAuth connections). |
Slack message model
PRFlow keeps a 1:1 mapping between a merge request and a Slack message. The first qualifying event posts the message to the channel you mapped; every later event updates that same message (Slackchat.update) instead of
posting a new one. If a project is mapped to multiple channels, each channel
gets at most one message per MR, and updates stay in the channel where the
message was originally posted.
The message shows:
- a status emoji for the MR state (new / approved 👍 / merged ✅ / closed ❌),
- the MR number and title, linked to the MR in GitLab,
- author display name and username, target branch, and project name,
- inline CI status when pipeline events arrive (✅ passed, ❌ failed, 🔄 running, 🚫 canceled, ⏭️ skipped, 👆 manual),
- actionable blockers from GitLab’s
detailed_merge_status— e.g. “Needs approvals”, “Has unresolved discussions”, “CI failed”, “Needs rebase”, “Has merge conflicts”.
Setup guide
Setup takes a few minutes in the PRFlow web app at app.prflow.dev. You connect GitLab, point a webhook at PRFlow, connect Slack, and map projects to channels.Sign in and pick a workspace
Sign in at app.prflow.dev and create or select a
workspace. Each PRFlow workspace connects to one GitLab organization
(top-level group) and one Slack workspace.
Connect GitLab
From Home or Settings → Connections, choose your instance type:
- gitlab.com — connect via OAuth (PRFlow requests only the
read_apiscope; tokens auto-refresh), or save a personal access token instead. - Self-managed GitLab — enter your instance root URL (HTTPS required;
PRFlow appends
/api/v4automatically) and save a personal access token. OAuth is not available for self-managed instances.
read_api scope and access to the
GitLab group you want PRFlow to use. PRFlow validates the token on save —
scope, expiry, and revocation — and recommends a dedicated service-account
token over a personal one to reduce operational risk. Token health
(expiring, expired, revoked) is surfaced on the dashboard afterwards.During connection, PRFlow resolves the top-level GitLab group your
credential can access and binds the workspace to it. One GitLab
organization maps to one PRFlow workspace — connecting the same GitLab
group to a second workspace is rejected.Add the webhook in GitLab
PRFlow shows your workspace’s unique webhook URL under
Settings → Connections → GitLab Webhook. In GitLab, add it as either:
- Group webhook (recommended) — add the URL once on your top-level group (Group → Settings → Webhooks). Covers all projects in the group. Requires the Owner role and GitLab Premium or Ultimate.
- Per-project webhooks — add the same URL to each project (Project → Settings → Webhooks). Works on every GitLab tier, including Free. Requires Maintainer or Owner on each project.
X-Gitlab-Token header on every delivery.Connect Slack
Authorize PRFlow’s Slack app via OAuth. PRFlow requests four bot scopes:
chat:write, chat:write.public, channels:read, and groups:read —
enough to list channels and post/update messages, nothing more.Map projects to channels
Click Add repositories, pick projects from your GitLab group (PRFlow
lists projects where your credential has at least Developer access,
subgroups included), and choose the Slack channel each one should notify.
From then on, every non-draft merge request in those projects posts itself
to the mapped channel.
GitLab compatibility: gitlab.com on any tier (group webhooks need
Premium or Ultimate; per-project webhooks work on Free), and self-managed
GitLab instances reachable over HTTPS. Self-managed instances behind a
firewall need outbound HTTPS to
app.prflow.dev for webhooks; PRFlow’s API
polling additionally requires the instance to be reachable from the
internet.Data handling
When you connect PRFlow, merge request metadata leaves your GitLab instance: GitLab transmits it to PRFlow via webhooks and API responses, and PRFlow forwards a subset of it into the Slack workspace you connect. This section spells out exactly what moves and what is stored. See the privacy policy for the full legal terms.Data received from GitLab
From the three webhook event types and the API calls listed above, PRFlow receives:- Project metadata: ID, name, path, web URL.
- Merge request metadata: IID, title, description, state, action, source/target branch, author, draft flag, detailed merge status, timestamps, web URL.
- Comment (note) data: note ID, comment text, author username, action, URL.
- Pipeline data: pipeline ID, status, ref, duration.
- Approval and discussion summaries: approvals remaining, unresolved discussion count.
- Connected-account info: GitLab user ID, username, display name, avatar URL; group names and paths.
read_api credential is used exclusively for the endpoints documented
above.
Data sent to Slack
Notifications in your Slack channels contain:- MR number, title, and a link to the MR in GitLab,
- author display name and username, target branch, project name,
- CI pipeline status, approval/discussion indicators, and merge-status labels,
- comment text excerpts (up to 500 characters) and commenter usernames in thread replies.
What PRFlow stores
- Connection records: your workspace, the connected GitLab account ID/username, the bound top-level group, and the instance URL for self-managed GitLab.
- Credentials: OAuth access/refresh tokens, personal access tokens, and the optional webhook secret — all encrypted at rest with AES-256-GCM.
- Repository and channel-mapping configuration for the projects you add.
- Per-MR metadata needed to keep the Slack message current: title, state, branches, web URL, author username and display name, pipeline status, detailed merge status, approval/discussion counts, and timestamps.
- Slack message references (channel ID, message timestamp) for each MR message and each synced comment.
Permissions & security
GitLab scopes
| Scope | Why PRFlow needs it |
|---|---|
read_api | The single scope PRFlow requests — for OAuth and for personal access tokens alike. It covers listing groups and projects during setup, reading MR approval and discussion state, and the polling fallback. No write scope is ever requested; PRFlow cannot modify code, MRs, settings, or anything else in GitLab. |
Slack scopes
| Scope | Why PRFlow needs it |
|---|---|
chat:write | Post the MR message, update it in place, and manage threaded comment replies. |
chat:write.public | Post to public channels without requiring a manual bot invite to each one. |
channels:read | List public channels in the channel picker when you map a project. |
groups:read | List private channels the bot has been added to, so they appear in the picker too. |
Webhook endpoint security
- Each workspace gets a unique, unguessable webhook URL; deliveries are matched to exactly one workspace.
- When you configure a secret token, PRFlow verifies the
X-Gitlab-Tokenheader on every delivery using a constant-time comparison and rejects mismatches with HTTP 401. The secret is stored encrypted. Leaving the secret blank (URL-only mode) is supported but the secret is recommended. - Events whose project host does not match the connected GitLab instance are rejected.
- Events for projects you have not added are acknowledged and discarded without processing.
- Webhook payloads are capped at 10 MB to prevent resource-exhaustion abuse.
Credentials and transport
- The gitlab.com OAuth flow uses the standard authorization-code grant with CSRF state validation (state values are stored as SHA-256 hashes and expire after 10 minutes).
- GitLab and Slack tokens are encrypted at rest with AES-256-GCM and are never written to logs.
- OAuth access tokens are refreshed automatically before expiry; 401/403 responses from GitLab flag the credential as unhealthy on your dashboard so you can rotate it.
- All traffic — GitLab webhooks in, GitLab API calls out, Slack API calls out — runs over HTTPS. Self-managed instance URLs must be HTTPS.