Skip to main content

Slack Integration

This guide covers the integration between NopeSight and Slack, enabling real-time notifications, interactive commands, and ChatOps capabilities for your IT operations.

Overview

The NopeSight-Slack integration provides:

  • Real-time Notifications - Instant alerts for critical events
  • Interactive Commands - Execute NopeSight actions from Slack
  • ChatOps Automation - Manage infrastructure through conversation
  • Custom Workflows - Build automated responses with Slack workflows
  • Rich Formatting - Detailed messages with charts and visualizations

Architecture

Installation

1. Create Slack App

  1. Go to api.slack.com/apps
  2. Click Create New App
  3. Choose From scratch
  4. Name your app: NopeSight
  5. Select your workspace

2. Configure OAuth & Permissions

# Required OAuth Scopes
bot_token_scopes:
- channels:history # Read channel messages
- channels:read # List channels
- chat:write # Send messages
- chat:write.public # Send to any channel
- commands # Receive slash commands
- files:write # Upload files (reports)
- im:history # Read DMs
- im:write # Send DMs
- users:read # Get user info
- users:read.email # Match users by email

user_token_scopes:
- channels:history
- channels:read
- chat:write
- files:read

3. Install to Workspace

# Install app and get tokens
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
SLACK_SIGNING_SECRET=your-signing-secret

# Configure NopeSight
curl -X POST https://api.nopesight.com/v1/integrations/slack/setup \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"bot_token": "'$SLACK_BOT_TOKEN'",
"app_token": "'$SLACK_APP_TOKEN'",
"signing_secret": "'$SLACK_SIGNING_SECRET'"
}'

Configuration

Environment Variables

# Slack configuration
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
SLACK_SIGNING_SECRET=your-signing-secret
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX

# Channel configuration
SLACK_ALERTS_CHANNEL="#alerts"
SLACK_INCIDENTS_CHANNEL="#incidents"
SLACK_CHANGES_CHANNEL="#changes"
SLACK_REPORTS_CHANNEL="#reports"

Channel Mapping

# NopeSight configuration
integrations:
slack:
enabled: true

channel_mappings:
# Event severity to channel mapping
critical: "#incidents"
high: "#alerts"
medium: "#alerts"
low: "#notifications"

notification_rules:
- name: "Critical Infrastructure"
condition: "severity == 'critical' AND ci_type == 'server'"
channel: "#infrastructure-critical"
mentions: ["@oncall", "@infrastructure-team"]

- name: "Security Events"
condition: "category == 'security'"
channel: "#security-alerts"
mentions: ["@security-team"]

formatting:
use_blocks: true
include_charts: true
thread_replies: true
emoji_reactions: true

Slash Commands

Command Registration

// Register slash commands in Slack app
const slashCommands = [
{
command: '/nopesight',
description: 'Interact with NopeSight CMDB and monitoring',
usage_hint: '[status|search|report|help] [options]'
},
{
command: '/cmdb',
description: 'Query configuration items',
usage_hint: '[search|show|update] [query]'
},
{
command: '/incident',
description: 'Manage incidents',
usage_hint: '[create|update|resolve] [options]'
}
];

Command Handler

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import re

app = App(token=SLACK_BOT_TOKEN)

@app.command("/nopesight")
def handle_nopesight_command(ack, body, client):
"""Handle /nopesight commands"""
ack()

command_text = body['text']
user_id = body['user_id']
channel_id = body['channel_id']

# Parse command
parts = command_text.split()
if not parts:
show_help(client, channel_id, user_id)
return

action = parts[0].lower()
args = parts[1:] if len(parts) > 1 else []

# Route to appropriate handler
if action == 'status':
show_system_status(client, channel_id, args)
elif action == 'search':
search_cmdb(client, channel_id, user_id, ' '.join(args))
elif action == 'report':
generate_report(client, channel_id, user_id, args)
elif action == 'help':
show_help(client, channel_id, user_id)
else:
client.chat_postEphemeral(
channel=channel_id,
user=user_id,
text=f"Unknown command: {action}. Type `/nopesight help` for available commands."
)

def show_system_status(client, channel_id, args):
"""Show system status"""

# Get status from NopeSight
status = nopesight_api.get_system_status()

# Build status message
blocks = [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "🟢 NopeSight System Status"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": f"*API Status:* {status['api']['status']}"
},
{
"type": "mrkdwn",
"text": f"*Response Time:* {status['api']['response_time']}ms"
},
{
"type": "mrkdwn",
"text": f"*Active Agents:* {status['agents']['active']}/{status['agents']['total']}"
},
{
"type": "mrkdwn",
"text": f"*Open Events:* {status['events']['open']}"
}
]
},
{
"type": "divider"
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": f"Last updated: <!date^{int(time.time())}^{{date_short}} {{time}}|{datetime.now()}>"
}
]
}
]

client.chat_postMessage(
channel=channel_id,
blocks=blocks
)

@app.command("/cmdb")
def handle_cmdb_command(ack, body, client):
"""Handle /cmdb commands"""
ack()

command_text = body['text']
user_id = body['user_id']

# Parse search query
match = re.match(r'(search|show|update)\s+(.*)', command_text)
if not match:
show_cmdb_help(client, body['channel_id'], user_id)
return

action = match.group(1)
query = match.group(2)

if action == 'search':
search_results = nopesight_api.search_ci(query)
display_search_results(client, body['channel_id'], search_results, query)
elif action == 'show':
ci_details = nopesight_api.get_ci(query)
display_ci_details(client, body['channel_id'], ci_details)
elif action == 'update':
# Show update modal
show_update_modal(client, body['trigger_id'], query)

Interactive Components

Message Actions

// Interactive message with actions
const interactiveMessage = {
channel: channelId,
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `🚨 *Critical Alert*: ${event.title}\n*Severity:* ${event.severity}\n*Time:* ${event.timestamp}`
}
},
{
type: "section",
text: {
type: "mrkdwn",
text: event.description
},
accessory: {
type: "button",
text: {
type: "plain_text",
text: "View Details"
},
url: `https://nopesight.com/events/${event.id}`
}
},
{
type: "actions",
elements: [
{
type: "button",
text: {
type: "plain_text",
text: "Acknowledge"
},
style: "primary",
action_id: "acknowledge_event",
value: event.id
},
{
type: "button",
text: {
type: "plain_text",
text: "Create Incident"
},
action_id: "create_incident",
value: event.id
},
{
type: "button",
text: {
type: "plain_text",
text: "Run Playbook"
},
action_id: "run_playbook",
value: event.id
}
]
}
]
};
@app.action("create_incident")
def handle_create_incident(ack, body, client):
"""Show incident creation modal"""
ack()

event_id = body['actions'][0]['value']
event = nopesight_api.get_event(event_id)

# Open modal
client.views_open(
trigger_id=body['trigger_id'],
view={
"type": "modal",
"callback_id": "incident_modal",
"title": {
"type": "plain_text",
"text": "Create Incident"
},
"submit": {
"type": "plain_text",
"text": "Create"
},
"private_metadata": event_id,
"blocks": [
{
"type": "input",
"block_id": "title",
"element": {
"type": "plain_text_input",
"action_id": "title_input",
"initial_value": event['title']
},
"label": {
"type": "plain_text",
"text": "Incident Title"
}
},
{
"type": "input",
"block_id": "priority",
"element": {
"type": "static_select",
"action_id": "priority_select",
"initial_option": {
"text": {
"type": "plain_text",
"text": "High"
},
"value": "high"
},
"options": [
{
"text": {"type": "plain_text", "text": "Critical"},
"value": "critical"
},
{
"text": {"type": "plain_text", "text": "High"},
"value": "high"
},
{
"text": {"type": "plain_text", "text": "Medium"},
"value": "medium"
},
{
"text": {"type": "plain_text", "text": "Low"},
"value": "low"
}
]
},
"label": {
"type": "plain_text",
"text": "Priority"
}
},
{
"type": "input",
"block_id": "assignee",
"element": {
"type": "users_select",
"action_id": "assignee_select"
},
"label": {
"type": "plain_text",
"text": "Assign To"
},
"optional": true
}
]
}
)

Notification Templates

Event Notifications

class SlackNotificationTemplates:

@staticmethod
def format_event_notification(event):
"""Format event as Slack message"""

# Determine emoji based on severity
emoji_map = {
'critical': '🚨',
'high': '⚠️',
'medium': '📊',
'low': 'ℹ️'
}

emoji = emoji_map.get(event['severity'], '📌')

# Build message blocks
blocks = [
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"{emoji} {event['title']}"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": f"*Severity:* {event['severity'].upper()}"
},
{
"type": "mrkdwn",
"text": f"*Source:* {event['source']}"
},
{
"type": "mrkdwn",
"text": f"*Time:* <!date^{int(event['timestamp'])}^{{date}} {{time}}|{event['timestamp']}>"
},
{
"type": "mrkdwn",
"text": f"*Status:* {event['status']}"
}
]
}
]

# Add description
if event.get('description'):
blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": event['description']
}
})

# Add affected CIs
if event.get('affected_cis'):
ci_list = '\n'.join([f"• <{CI_URL}/{ci['id']}|{ci['name']}>" for ci in event['affected_cis'][:5]])
if len(event['affected_cis']) > 5:
ci_list += f"\n• _and {len(event['affected_cis']) - 5} more..._"

blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Affected CIs:*\n{ci_list}"
}
})

# Add AI insights if available
if event.get('ai_analysis'):
blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*AI Analysis:*\n{event['ai_analysis']['summary']}"
}
})

if event['ai_analysis'].get('recommended_action'):
blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Recommended Action:*\n{event['ai_analysis']['recommended_action']}"
}
})

# Add actions
blocks.append({
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Details"
},
"url": f"{WEB_URL}/events/{event['id']}"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Acknowledge"
},
"action_id": "acknowledge_event",
"value": event['id']
}
]
})

return blocks

Report Notifications

// Report generation notification
async function notifyReportComplete(report, channel) {
const message = {
channel: channel,
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `📊 *Report Generated Successfully*\n*Report:* ${report.name}\n*Type:* ${report.type}\n*Period:* ${report.period}`
},
accessory: {
type: "image",
image_url: report.preview_url,
alt_text: "Report preview"
}
},
{
type: "section",
text: {
type: "mrkdwn",
text: `*Summary:*\n${report.summary}`
}
},
{
type: "actions",
elements: [
{
type: "button",
text: {
type: "plain_text",
text: "Download PDF"
},
url: report.pdf_url
},
{
type: "button",
text: {
type: "plain_text",
text: "View Online"
},
url: report.web_url
},
{
type: "button",
text: {
type: "plain_text",
text: "Share"
},
action_id: "share_report",
value: report.id
}
]
}
]
};

// Upload report as file
if (report.attach_file) {
await slack.files.upload({
channels: channel,
file: report.file_path,
filename: `${report.name}.pdf`,
initial_comment: "Full report attached"
});
}

return await slack.chat.postMessage(message);
}

Workflows & Automation

Slack Workflow Integration

# Workflow trigger configuration
workflows:
- name: "Incident Response"
trigger:
type: "webhook"
url: "https://api.nopesight.com/slack/workflows/incident-response"

inputs:
- name: "severity"
type: "select"
options: ["critical", "high", "medium", "low"]
- name: "affected_service"
type: "text"
- name: "description"
type: "text"

actions:
- type: "create_incident"
params:
title: "{{workflow.title}}"
severity: "{{inputs.severity}}"
description: "{{inputs.description}}"

- type: "notify_team"
params:
channel: "#incidents"
mentions: ["@oncall"]

- type: "run_diagnostics"
params:
service: "{{inputs.affected_service}}"
auto_remediate: true

Custom Bot Commands

@app.message(re.compile(r"^nopesight (.*)", re.IGNORECASE))
def handle_ai_query(message, say, client):
"""Handle natural language queries"""

query = message['text'].replace('nopesight ', '', 1)
user = message['user']
channel = message['channel']

# Show typing indicator
client.chat_postMessage(
channel=channel,
text="Thinking... 🤔",
thread_ts=message.get('thread_ts')
)

# Process with AI
try:
response = nopesight_ai.process_query(query, context={
'user': user,
'channel': channel
})

# Format response
if response['type'] == 'data':
# Display data in table/chart
display_data_response(say, response['data'], message.get('thread_ts'))
elif response['type'] == 'action':
# Confirm action
confirm_action(client, channel, user, response['action'], message.get('thread_ts'))
else:
# Text response
say(
text=response['answer'],
thread_ts=message.get('thread_ts'),
mrkdwn=True
)

except Exception as e:
say(
text=f"Sorry, I couldn't process that request: {str(e)}",
thread_ts=message.get('thread_ts')
)

Advanced Features

Scheduled Reports

# Schedule daily summary
@app.event("app_mention")
def handle_mention(event, say):
"""Handle @NopeSight mentions"""

text = event['text']

if 'schedule daily summary' in text.lower():
# Parse time
time_match = re.search(r'at (\d{1,2}):(\d{2})\s*(am|pm)?', text, re.I)
if time_match:
hour = int(time_match.group(1))
minute = int(time_match.group(2))
meridiem = time_match.group(3)

if meridiem:
if meridiem.lower() == 'pm' and hour != 12:
hour += 12
elif meridiem.lower() == 'am' and hour == 12:
hour = 0

# Schedule report
schedule_daily_summary(
channel=event['channel'],
time={'hour': hour, 'minute': minute},
user=event['user']
)

say(f"✅ Daily summary scheduled for {hour:02d}:{minute:02d} in this channel")

Thread-based Incident Management

// Create incident thread
async function createIncidentThread(event) {
// Post initial message
const message = await slack.chat.postMessage({
channel: INCIDENTS_CHANNEL,
text: `New incident: ${event.title}`,
blocks: formatIncidentMessage(event)
});

// Create thread with timeline
await slack.chat.postMessage({
channel: INCIDENTS_CHANNEL,
thread_ts: message.ts,
text: "Incident Timeline:",
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*${new Date().toISOString()}* - Incident created\n*Severity:* ${event.severity}\n*Initial Assessment:* ${event.description}`
}
}
]
});

// Auto-invite relevant users
const relevantUsers = await getRelevantUsers(event);
for (const user of relevantUsers) {
await slack.chat.postMessage({
channel: user,
text: `You've been added to an incident thread: ${event.title}`,
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `You've been included in an incident response thread because you're associated with affected systems.`
},
accessory: {
type: "button",
text: {
type: "plain_text",
text: "Go to Thread"
},
url: `https://slack.com/app_redirect?channel=${INCIDENTS_CHANNEL}&thread_ts=${message.ts}`
}
}
]
});
}

return message.ts;
}

Security

Request Verification

from slack_sdk.signature import SignatureVerifier
import hmac
import hashlib

def verify_slack_request(request):
"""Verify request is from Slack"""

signature_verifier = SignatureVerifier(SLACK_SIGNING_SECRET)

# Get headers
timestamp = request.headers.get('X-Slack-Request-Timestamp')
signature = request.headers.get('X-Slack-Signature')

# Verify timestamp (prevent replay attacks)
if abs(time.time() - int(timestamp)) > 60 * 5:
return False

# Verify signature
return signature_verifier.is_valid_request(
request.get_data(),
timestamp,
signature
)

@app.before_request
def before_request():
if not verify_slack_request(request):
abort(403)

Permission Management

# User permission mapping
permissions:
roles:
admin:
commands: ["*"]
actions: ["*"]

operator:
commands: ["status", "search", "report"]
actions: ["acknowledge", "comment", "assign"]

viewer:
commands: ["status", "search"]
actions: ["view"]

user_mappings:
"U12345678": "admin" # Slack user ID to role
"U87654321": "operator"
default: "viewer"

Troubleshooting

Common Issues

troubleshooting:
connection_errors:
symptom: "Bot appears offline"
causes:
- Invalid bot token
- Network connectivity
- Rate limiting
solutions:
- Verify bot token
- Check firewall rules
- Implement exponential backoff

missing_messages:
symptom: "Bot doesn't respond to commands"
causes:
- Missing event subscriptions
- Incorrect permissions
- Bot not in channel
solutions:
- Check event subscriptions
- Verify OAuth scopes
- Invite bot to channel

formatting_issues:
symptom: "Messages appear broken"
causes:
- Invalid block JSON
- Unsupported markdown
- Character encoding
solutions:
- Validate block kit JSON
- Use Slack's markdown subset
- Ensure UTF-8 encoding

Best Practices

1. Message Design

  • Use Block Kit for rich formatting
  • Keep messages concise
  • Use threads for detailed discussions
  • Include actionable buttons

2. Performance

  • Cache Slack user data
  • Batch API calls when possible
  • Use async processing for heavy operations
  • Implement proper rate limiting

3. User Experience

  • Provide clear command help
  • Use ephemeral messages for errors
  • Acknowledge actions immediately
  • Follow up in threads

4. Security

  • Always verify requests
  • Implement role-based access
  • Audit all actions
  • Encrypt sensitive data

Example Integration

# Complete Slack integration example
class NopeSightSlackBot:
def __init__(self):
self.app = App(
token=os.environ["SLACK_BOT_TOKEN"],
signing_secret=os.environ["SLACK_SIGNING_SECRET"]
)
self.nopesight = NopeSightAPI()
self.setup_handlers()

def setup_handlers(self):
"""Set up all event handlers"""

# Commands
self.app.command("/nopesight")(self.handle_nopesight_command)
self.app.command("/cmdb")(self.handle_cmdb_command)

# Actions
self.app.action("acknowledge_event")(self.handle_acknowledge)
self.app.action("create_incident")(self.handle_create_incident)

# Events
self.app.event("app_mention")(self.handle_mention)
self.app.event("message")(self.handle_message)

# Views
self.app.view("incident_modal")(self.handle_incident_submission)

def start(self):
"""Start the bot"""
handler = SocketModeHandler(self.app, os.environ["SLACK_APP_TOKEN"])
handler.start()

# Start bot
if __name__ == "__main__":
bot = NopeSightSlackBot()
bot.start()