An incoming webhook URL is a credential written as a link: whoever holds it can post into your channels as a trusted integration, and these URLs are routinely pasted into repositories, CI variables, tickets and chat threads. Because they look like configuration rather than secrets, they escape the rotation and ownership processes that cover API keys. This guide covers one job: finding exposed incoming webhook URLs for Discord, Microsoft Teams and Slack across code, tickets and chat, deciding which ones matter, and rotating them without breaking alerting. Platforms are listed alphabetically throughout. It is written for security engineers, secret scanning owners and the platform teams who run alerting and ChatOps. For signing and validating webhook deliveries, see the Webhook Secrets Security Guide.
Key takeaways
- The URL is the whole secret. Discord, Slack and Power Automate based Teams webhooks accept requests to the URL itself; no separate key is needed unless you add one.
- Search for shapes, not words. Each platform has a recognisable host and path, so scanners can match URLs in Git history, CI logs, ticket exports and chat archives with a short list of patterns.
- Old Teams connector URLs are a signal, not a live risk. Connector based incoming webhooks stopped working in May 2026; the live exposure is Workflows URLs built on Power Automate.
- Rotation means replace and repoint. Delete or regenerate the webhook, update every sender from a secrets store, then confirm the old URL fails.
- Ownership prevents the next leak. Each webhook needs a named owning team, a stored location and a review date, or it will be copied into the next ticket.
Why a leaked incoming webhook matters
Incoming webhooks are write paths into collaboration tools. Their impact is narrower than a bot token, but they carry trust: messages arrive formatted as the integration your staff already rely on for alerts, deployments or approvals.
- Internal phishing: a convincing "action required" card with a link, posted into an operations channel by a familiar integration name. ATT&CK tracks this as T1534 Internal Spearphishing.
- Alert noise and suppression: floods that bury real alerts or train responders to ignore a channel.
- Webhook abuse beyond posting: on Discord, the token in the URL also allows reading, modifying and deleting the webhook itself.
- Exfiltration channels: attackers also use webhooks they control to move data out, tracked as T1567.004 Exfiltration Over Webhook. Your own leaked URLs can be repurposed the same way, with the stolen data landing in your channel.
Slack states that its app incoming webhook URLs are channel specific and tied to the app's identity, and cannot be used to post as arbitrary users or to unapproved channels. That limits blast radius, but it is the reason a fake message is believed.
What each platform's webhook URL looks like
| Platform | URL shape (from vendor documentation) | What the URL allows | Detection anchor |
|---|---|---|---|
| Discord | https://discord.com/api/webhooks/{webhook.id}/{webhook.token} | Execute (POST), get, modify (PATCH) and delete with the token, with no other authentication | /api/webhooks/ followed by a numeric ID and a token |
| Microsoft Teams (Workflows) | A Power Automate trigger URL; Microsoft documents HTTP trigger URLs as .../workflows/<workflowid>/triggers/manual/paths/invoke?api-version=2016-06-01&sp=...&sv=1.0&sig=<value> | Starts the workflow, which posts to the chosen chat or channel; authentication depends on the "Who can trigger the flow" setting | /triggers/manual/paths/invoke together with sig= |
| Microsoft Teams (retired connectors) | https://<tenant>.webhook.office.com/webhookb2/.../IncomingWebhook/... | Nothing since the May 2026 retirement; marks an integration that was rebuilt or abandoned | webhook.office.com and webhookb2 |
| Slack (app webhooks) | https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX; webhooks created through OAuth use a shorter hooks.slack.com form; GovSlack uses slack-gov.com | Posts into the one channel chosen at install, as the app | hooks.slack.com/services/ |
| Slack (legacy custom integrations) | Same hooks.slack.com host | Posts and can override channel, username and icon at runtime | As above; confirm in the custom integrations admin page |
Slack's documentation says plainly that a webhook URL contains a secret and should not be shared online, including in public version control. Slack also states that it actively searches out and revokes leaked secrets, so a URL found in a public repository may already be dead; still treat it as exposed and replace it.
Where exposed webhook URLs hide
| Location | Typical cause | How to search it |
|---|---|---|
| Git repositories and history | Alert scripts, monitoring configs, Terraform variables, notebooks | Secret scanner over full history on all branches, plus custom patterns |
| CI/CD settings and logs | URL in plain pipeline variables or echoed by a curl step | Export variables and job logs; scan with the same rules |
| Container images and packages | Config files baked into images or published packages | Image and artefact scanning with the webhook rules enabled |
| Tickets and wikis | "Here is the webhook for the new alert" in Jira, ServiceNow or Confluence | Platform search or export API filtered on the host strings |
| Chat history | URLs shared in DMs and channels while setting up integrations | eDiscovery or export search; see the How to find secrets in Slack and Teams messages guide |
| Front end code and mobile apps | Client posting feedback or crash reports straight to a channel | Scan built bundles and app packages, not only source |
Detection patterns and scanner coverage
- Gitleaks default rules: the project's default configuration includes slack-webhook-url, which matches hooks.slack.com paths under services, workflows and triggers, and microsoft-teams-webhook, which matches the retired webhook.office.com/webhookb2 connector format. Its Discord rules target API tokens and client secrets rather than webhook URLs, so add a custom rule for Discord.
- GitHub custom patterns: secret scanning lets repository, organisation and enterprise admins define custom patterns, dry run them against a sample of up to 1,000 matches, publish to scan full history, and then turn on push protection for the pattern. Add patterns for the Discord path and the Power Automate trigger path with sig=. See the Git Secret Scanning Guide.
- Check rule coverage first: confirm which webhook formats your scanners already detect before writing new rules. See the Secret Scanner Rule Coverage Comparison Guide.
- Keep matches tight: anchor on host and path, and for Teams require both /triggers/manual/paths/invoke and a sig= parameter. Broad keyword rules on "webhook" produce noise that teams learn to ignore.
- Do not paste findings back into chat: route alerts to the ticketing queue with the URL masked, or the triage process creates a new leak.
Triage: which findings matter first
- Classify the platform and format. Retired Teams connector URLs go to clean up; Workflows, Discord and Slack URLs go to live triage.
- Locate the exposure scope. Public repository, package or website is highest; internal repository with wide read access next; a private ticket or DM lowest, but still rotate.
- Identify the destination. For Slack app webhooks, the app's Incoming Webhooks settings page lists the URLs under "WebHook URLs for Your Workspace" with their channels. For Discord, the get with token endpoint returns the webhook object for the URL you hold. For Teams, find the workflow in Power Automate or the Workflows app and note its owner.
- Find the owner. Use the commit author, the ticket requester or the workflow owner. Microsoft notes that workflows are tied to owners rather than to the team or channel, so an owner who has left means an orphan flow.
- Check for misuse. Review the destination channel for messages that the expected sender did not produce, and on Discord check the server audit log for webhook changes.
- Decide urgency. Public exposure or a channel used for approvals or incident response means rotate today.
Avoid testing someone else's webhook by posting to it. For your own Slack URL, the documented error codes help confirm state after rotation: no_service means the webhook is disabled, removed or invalid, and no_active_hooks means it is disabled.
Rotating each platform's webhook
Discord
- Replace: a member with the Manage Webhooks permission creates a new webhook for the channel and stores its URL in the secrets manager.
- Remove the old one: delete the leaked webhook through the authenticated DELETE /webhooks/{webhook.id} endpoint (requires Manage Webhooks) or your server's integration settings. The delete with token endpoint also works with only the URL, which is useful when the owner cannot be found quickly in your own server.
- Record why: authenticated create, modify and delete calls accept an X-Audit-Log-Reason header, so the reason appears in the server audit log.
- For bot tokens and wider Discord hardening, see the Discord and Telegram Bot Tokens Security Guide.
Microsoft Teams
- Regenerate or recreate: Microsoft documents regenerating the SAS key for an HTTP triggered flow, which invalidates the old key and changes the sig= value. The Teams documentation also describes deleting the workflow from Workflows, Home, Your workflows, More actions, Delete, and creating a new one, which produces a new URL.
- Tighten the trigger: set "Who can trigger the flow" to "Any user in my tenant" or "Specific users in my tenant", which require a bearer token from your tenant. Microsoft describes "Anyone" as a legacy setting with open access to anyone holding the URL.
- Add co-owners so the workflow survives staff changes.
- Retired connector URLs: remove them from code and configuration and confirm the alert path was migrated. See the Microsoft Teams Office 365 Connectors Retirement guide.
Slack
- Replace: on the app's Incoming Webhooks page, use "Add New Webhook to Workspace" for the same channel, then reinstall the app from the Install App page if prompted.
- Retire the old URL: remove it from the app configuration, or remove the integration entirely if it is no longer needed, then confirm a test post returns no_service.
- Legacy custom integrations: manage them from the custom integrations admin page. Slack recommends moving this functionality to a Slack app, which also removes the runtime channel and username override.
- For the wider app inventory, see the How to audit Slack apps, bots and tokens guide.
Rolling out a webhook leak programme
- Inventory: export the webhook lists from each platform (Slack app settings and custom integrations, Discord server integrations, Power Automate flows using the Teams webhook trigger) into a register with owner, destination, purpose and storage location.
- Baseline scan in report mode: run the patterns across all repositories, CI logs, ticket and wiki exports and chat archives; do not block anything yet.
- Rotate by priority: public and approval channels first, then anything in shared repositories, then the rest. Change senders to read the URL from the secrets manager before deleting the old webhook, so alerting keeps flowing.
- Test: send a test message from each updated sender, confirm it arrives, then confirm the old URL fails.
- Roll back if needed: Teams workflows can be turned off and on again, so turn off rather than delete during the change window; for Discord and Slack, keep the new URL deployed and tested before removing the old one.
- Enforce: turn on push protection for the webhook patterns, then add the patterns to pre-commit hooks and CI checks.
Decision guidance
- Webhook or bot: keep webhooks for one way alerts into one channel; move to a bot or app with scoped tokens when you need threads, edits, multiple channels or interaction.
- Small teams: enable the default scanner rules plus one Discord custom rule, keep a simple register, and rotate on every leak. That covers most risk.
- Large organisations: centralise webhook creation through the platform team, require storage in the secrets manager, and restrict Teams triggers to tenant users where senders can obtain an Entra ID token.
- Senders outside your tenant: if an external service must post to Teams, accept that the URL is the credential, keep the destination low trust, and rotate on a schedule.
Detection and measurement
- Monitor: scanner alerts for webhook patterns; Discord audit log entries for webhook create, update and delete; new Power Automate flows using the Teams webhook trigger with "Anyone" access; messages in alert channels whose format does not match the expected sender.
- Metrics:
- Webhook URLs found per month by location (repository, CI, ticket, chat) and the trend.
- Median time from detection to rotation.
- Percentage of webhooks in the register with a named owner and secrets manager storage.
- Percentage of Teams webhook workflows restricted to tenant users.
- Count of retired connector URLs still present in code.
Framework mapping
| Framework | Reference | How it applies |
|---|---|---|
| MITRE ATT&CK | T1552.008 Chat Messages; T1213.003 Code Repositories | Webhook URLs collected from chat and repositories |
| MITRE ATT&CK | T1534 Internal Spearphishing; T1567.004 Exfiltration Over Webhook | Abuse of a leaked URL to post lures or carry data |
| NIST SP 800-53 Rev 5 | IA-5, CM-8, AU-6, IR-4 | Authenticator management, component inventory, log review, incident handling |
| NIST CSF 2.0 | ID.AM-02, PR.AA-01, DE.CM-09, RS.MI-01 | Inventory of services, credential management, monitoring, containment |
| ISO/IEC 27001:2022 Annex A | 5.17 Authentication information, 8.12 Data leakage prevention, 8.16 Monitoring activities | Treat URLs as authentication information; detect and monitor leaks |
Common failure patterns
- Deleting the message, not the webhook. Removing a chat post or rewriting Git history leaves the URL valid in every clone, fork and export.
- Rotating before updating senders. Alerting silently stops, and someone pastes a new URL into a ticket to fix it fast.
- Keyword rules only. Matching the word "webhook" floods triage; matching host and path finds real URLs.
- Ignoring Discord. Default rule sets often cover Slack and the old Teams format but not Discord webhook URLs.
- Leaving Teams triggers open. "Anyone" access on a workflow keeps the URL as a bearer credential when tenant restriction would work.
- Orphaned owners. A workflow or webhook owned by a departed employee is never rotated. Assign a team, not a person, as owner.
Practitioner checklist
- Build a register of every incoming webhook with owner, destination and storage location.
- Enable scanner rules for Slack hooks and add custom rules for Discord webhooks and Power Automate trigger URLs with sig=.
- Scan full Git history, CI variables and logs, images, tickets, wikis and chat exports.
- Mask URLs in scanner alerts and route them to tickets, not chat.
- Update senders to read URLs from a secrets manager before rotating.
- Rotate leaked webhooks per platform and confirm the old URL fails.
- Restrict Teams webhook workflows to tenant users and add co-owners.
- Migrate Slack legacy custom integrations to apps.
- Turn on push protection for webhook patterns once false positives are tuned.
- Report the metrics above monthly.
Standards and references
- Slack: Sending messages using incoming webhooks
- Slack: Best practices for security
- Discord Developer Documentation: Webhook resource
- Microsoft Learn: Create incoming webhooks with Workflows for Microsoft Teams
- Microsoft Learn: Add OAuth authentication for HTTP request triggers
- Microsoft Learn: Regenerate the SAS key used in HTTP trigger flows
- Gitleaks: Default rule configuration
- GitHub Docs: Defining custom patterns for secret scanning
Related NHI Mgmt Group resources: Webhook Secrets Security Guide · How to find secrets in Slack and Teams messages · Developer Secret Scanning Triage · Leaked Credential Response Playbook