PRFlow

Technical documentation

PRFlow GitLab integration

How PRFlow connects to GitLab, which webhook events and API calls it uses, what it sends to Slack, and exactly what merge request data it handles along the way.

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:

  1. GitLab webhooks — your GitLab group or projects send merge request, comment, and pipeline events to a per-workspace PRFlow endpoint in real time.
  2. Read-only GitLab API access — PRFlow uses a credential with the read_api scope 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.
  3. Slack Web API — PRFlow posts and updates messages in the channels you map, using a Slack bot token obtained via OAuth.
GitLab ── webhooks (MR / note / pipeline events) ──▶ PRFlow ── chat.postMessage /
       ◀── read-only API calls (read_api) ─────────┘          chat.update ──▶ Slack

PRFlow works with gitlab.com (OAuth or personal access token) and self-managed GitLab instances reachable over HTTPS (personal access token). It never requests write access to GitLab and never reads repository code or diffs — only merge request, comment, and pipeline metadata.

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.

Events for projects you have not added in PRFlow are acknowledged and ignored. This makes a single group-level webhook safe: GitLab can fan out events for every project in the group, and PRFlow only acts on the ones you mapped to a Slack channel.

GitLab API calls

All data calls are read-only GET 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 (Slack chat.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".

Comment sync: each GitLab comment on the MR becomes a threaded reply under the MR message, quoting the comment text (truncated to 500 characters) with the commenter's username. Comment edits in GitLab update the existing thread reply; comment deletions remove it. If an MR has no parent message in Slack (for example, the project was connected after the MR was opened), comments are not posted.

Ordering and duplicates: PRFlow timestamps each event and rejects stale, out-of-order updates at the database layer before touching Slack, and uses atomic claim records so concurrent webhook deliveries never produce duplicate messages. MRs created before a project was connected to PRFlow are not announced retroactively.

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.

01

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.

02

Connect GitLab

From Home or Settings → Connections, choose your instance type:

  • gitlab.com — connect via OAuth (PRFlow requests only the read_api scope; tokens auto-refresh), or save a personal access token instead.
  • Self-managed GitLab — enter your instance root URL (HTTPS required; PRFlow appends /api/v4 automatically) and save a personal access token. OAuth is not available for self-managed instances.

Personal access tokens must have the 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.

03

Add the webhook in GitLab

PRFlow shows your workspace's unique webhook URL under Settings → Connections → GitLab Webhook. In GitLab:

  • 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 on the group 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.

Enable the Merge request events, Comment events, and Pipeline events triggers, and keep SSL verification on. Optionally set a Secret token in GitLab and save the same value in PRFlow — PRFlow then verifies the X-Gitlab-Token header on every delivery.

04

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.

05

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.

PRFlow never requests repository file contents, diffs, or commit contents. The 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.

Comment bodies are not stored. PRFlow keeps only the GitLab note ID and the Slack message reference, so it can edit or delete the threaded reply later; the comment text itself passes through to Slack and lives there.

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-Token header 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.

Support

Questions about the integration, setup help, or a webhook that isn't firing? Email hello@prflow.dev and we'll get you sorted.

Get Started Free