- Published on
GitLab Slack Webhook Security: Rotate Leaked URLs Safely
- Authors

- Name
- Maruan
A security bot pings your on-call channel at 2:00 AM. A developer committed a .gitlab-ci.yml file with a raw Slack incoming webhook URL to a public GitLab repository, or pasted it into an open issue description. Your first instinct is to panic and delete the integration from Slack. That stops the threat, but it also silently breaks pipeline alerts, deploy notifications, and merge request updates across dozens of active projects.
GitLab Slack webhook security during a live credential exposure calls for a calm, deliberate rotation. Identify the scope of the compromise, route notifications through a clean replacement URL, kill the exposed endpoint, and confirm the attacker can't push malicious payloads into your engineering workspace. Here is the runbook to rotate leaked webhook URLs without dropping legitimate engineering notifications.
Two different secrets: Slack webhook URL vs GitLab webhook secret token
Before touching any settings, identify which credential actually escaped. Engineers often mix up Slack incoming webhook URLs and GitLab webhook secret tokens because both live in configuration screens that handle HTTP payloads between the two platforms.
They run in opposite directions. A Slack incoming webhook URL is an inbound credential managed by Slack. Anyone who sends an HTTP POST request to that URL pushes a message straight into your Slack channel. GitLab is only the client sending that request. A GitLab webhook secret token is generated inside GitLab to sign outbound webhooks that GitLab sends to external listener services, or to authenticate incoming system hooks hitting GitLab's own API.
If you regenerate the GitLab webhook secret token after a Slack URL leak, you have done nothing to stop an attacker from abusing your Slack channel. The leaked URL stays fully functional on Slack's edge servers. If you rotate the Slack URL, you don't need to touch GitLab secret tokens used for downstream CI/CD triggers. A leaked Slack webhook URL means rotating an endpoint inside Slack, then updating GitLab's integration fields to point to the new destination.
What a leaked Slack webhook URL actually lets someone do
Knowing the threat keeps you from overreacting or underreacting. An incoming Slack webhook URL is a write-only pipe into one designated Slack channel. It gives no read access. Whoever holds the URL can't inspect your channel history, download shared snippets, see who is in the channel, or list other channels in your workspace.
Write access alone is still dangerous. The main risk is convincing internal phishing. Because the webhook posts through an authorized bot user, an attacker can broadcast realistic security warnings, fake login prompts, or malicious links asking engineers to re-authenticate against compromised identity providers. During an active production incident, an attacker can inject fabricated alerts claiming a service has recovered, which misleads responders and delays remediation.
Attackers can also spam messages until Slack hits rate limits (typically one request per second per webhook), flooding the channel and forcing engineers to mute critical alert streams. The 2024 Verizon Data Breach Investigations Report found that social engineering remains one of the top vectors for enterprise credential theft (Verizon DBIR, 2024). A trusted bot channel is an ideal staging ground for that attack, so treat an exposed webhook URL as a high-priority security incident.
Find every place the URL is configured (project, group, instance level)
Before generating a new credential, map out where the compromised URL lives. If you update the URL in one repository while three others keep sending alerts to the old endpoint, your deployment alerts break the moment you revoke the credential in Slack.
GitLab allows Slack notifications at three levels. First, check your GitLab group settings. If your team relies on GitLab Slack integration group defaults, a single group-level webhook URL may cascade down to hundreds of child repositories. Go to your top-level group, open Settings > Integrations, and inspect the Slack notifications configuration. Second, check individual project settings under Project Settings > Integrations > Slack notifications. Projects that override group defaults have their own independent webhook URLs.
Third, check instance-level settings if you run self-managed GitLab. Instance administrators can define global integration templates under Admin Area > Settings > Integrations.
Finally, search your CI/CD variables. Many teams skip GitLab's native Slack integration and pipe notifications through .gitlab-ci.yml jobs using curl. Search across your group for variables named SLACK_WEBHOOK, SLACK_WEBHOOK_URL, or CI_SLACK_WEBHOOK. Check both project-level and group-level CI/CD variable tables, and note whether each variable is masked and protected.
Replace the credential: create a new Slack webhook, update GitLab, then revoke the old one
Never revoke the leaked webhook before its replacement is configured. Revoking first causes an immediate outage for your deployment and pipeline alerts. Rotate additively instead: create, update, verify, then destroy.
Step one: Open your Slack workspace administration panel. If you use a custom Slack App, go to api.slack.com/apps, select your app, click Incoming Webhooks, and click 'Add New Webhook to Workspace'. Select the target channel to generate a new URL. If you use Slack's legacy Incoming WebHooks integration, go to your workspace's App Directory, find the integration configuration, and add a new configuration entry for that channel.
Step two: Copy the new webhook URL and paste it into GitLab. If the URL was configured at the group level, update the group settings first. GitLab updates all child projects that inherit group defaults. For projects with manual overrides, paste the new URL into their own Slack notifications integration panels. If your CI/CD pipelines use environment variables, update the variable value in your group or project settings and make sure the 'Mask variable' toggle is on.
Step three: Return to Slack and revoke the leaked credential. In your custom Slack App settings, scroll to the list of active webhook URLs, find the exposed one by its creation date or trailing token, and click the delete icon. If you use the legacy integration, scroll to the specific integration instance and select 'Remove'. The old URL is now dead on Slack's side.
Test the new route safely and avoid duplicate delivery
Once the new webhook is saved in GitLab, verify delivery right away. Go to your project or group Integrations page, open the Slack notifications settings, scroll to the bottom, and click 'Test settings and save changes'. GitLab sends an HTTP POST request with a test message to your designated Slack channel. Confirm the message appears in Slack.
Next, guard against duplicate delivery. Teams hit alert storms when several engineers patch notification issues at the same time by stacking different integrations. If one engineer configures a group-level Slack integration while another configures a project-level webhook or a custom CI curl script, your channel gets duplicate messages for every merge request and pipeline completion. Read our guide on how to fix GitLab Slack duplicate notifications during migration to make sure no conflicting systems broadcast the same events.
Managing brittle incoming webhook URLs across dozens of repositories quickly becomes a maintenance burden. MergeMe connects GitHub.com, GitLab.com, and self-hosted GitLab into Slack, and shows pull requests and merge requests as one updating card per PR or MR instead of flooding channels with loose webhook scripts. A unified service takes raw incoming webhook URLs out of your CI/CD scripts altogether.
Check logs and confirm the old URL is dead
Trusting that you clicked the right revoke button isn't enough. Probe the exposed endpoint and make sure Slack rejects requests sent to it.
Open a terminal and run a raw curl command against the old, leaked webhook URL:
curl -X POST -H 'Content-type: application/json' --data '{"text":"security-verification"}' https://hooks.slack.com/services/T00/B00/REVOKED_SECRET
Slack should return an error response indicating that the webhook is no longer valid. If the command returns HTTP 200 with the string ok, the old URL is still active. Recheck your Slack App dashboard right away to confirm you deleted the correct webhook instance.
Next, check GitLab for lingering pipelines or background jobs that still use the old endpoint. On self-managed GitLab, inspect the Rails production logs or review background worker logs for failed notification workers. If jobs are failing with 404 errors against Slack's domain, an unrotated repository or hardcoded CI script is still posting to the old credential. For debugging self-managed log trails, use our checklist for self-hosted GitLab Slack webhook troubleshooting.
Incident checklist and how to keep URLs out of repos and chat next time
Finish the rotation with an incident checklist and automated safeguards. The checklist must confirm five facts: the new webhook is verified in Slack, the old URL returns 404, all GitLab project and group integration fields are updated, CI/CD variables are masked, and channel history during the exposure window was reviewed for unauthorized messages.
To prevent future leaks, stop relying on manual vigilance. Developers paste credentials into code when configuration workflows make secrets hard to reach. First, enable GitLab Secret Push Protection. It is built into GitLab Ultimate, and tools like Gitleaks cover standard CI pipelines. Secret push protection scans commits and rejects pushes containing recognizable Slack webhook patterns.
Second, enforce pre-commit hooks using tools like TruffleHog across developer workstations. Catching a secret before git commit keeps the credential out of your repository's permanent Git history.
Third, audit who has maintainer rights in your GitLab projects. Only maintainers and owners can change integration settings and view CI variables. Restricting these permissions limits how many people can accidentally expose webhook URLs during daily work.
Conclusion
Leaked webhook URLs are annoying, but an additive rotation keeps an exposure from becoming an outage. Create the new credential, update GitLab, test delivery, and verify the old endpoint returns a 404 before you close the incident ticket.
If you're tired of managing loose incoming webhook URLs across disparate repositories, centralize your review workflows with MergeMe. MergeMe connects GitHub.com, GitLab.com, and self-hosted GitLab into Slack, organizing code reviews into one updating card per PR or MR. The Team plan is £5 per dev seat monthly, and teams with strict compliance needs can run MergeMe on their own infrastructure by contacting hello@mergeme.dev.
FAQs
Does deleting a Slack incoming webhook URL break my entire GitLab integration?
Other Slack apps, bots, or webhooks configured in your workspace remain unaffected. Always update GitLab with your replacement URL before deleting the old webhook in Slack to prevent alert downtime.
Can an attacker read private Slack messages using a leaked webhook URL?
No. The risk is limited to sending unauthorized messages, phishing attempts, and message spam within the connected channel.
Why does GitLab return a 404 error when testing my new Slack webhook?
Regenerate the webhook URL in your Slack app management dashboard and verify the full path is pasted into GitLab.
How do I mask a Slack webhook URL in GitLab CI/CD variables?
Navigate to Settings > CI/CD > Variables in your GitLab project or group. Add your variable with the raw URL as the value, and check the 'Mask variable' option. GitLab masks values of eligible masked CI/CD variables in job logs, where eligibility is determined by value requirements such as being single-line and at least 8 characters.