Technical documentation
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.
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:
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.
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.
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.
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). |
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:
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 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 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.
From Home or Settings → Connections, choose your instance type:
read_api scope; tokens auto-refresh), or save a personal access
token instead.
/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.
PRFlow shows your workspace's unique webhook URL under Settings → Connections → GitLab Webhook. In GitLab:
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.
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.
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.
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.
From the three webhook event types and the API calls listed above, PRFlow receives:
PRFlow never requests repository file contents, diffs, or commit contents. The
read_api credential is used exclusively for the endpoints documented above.
Notifications in your Slack channels contain:
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.
| 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. |
| 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. |
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.
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