Webhook to Slack Flow
A complete example flow that receives webhooks and sends notifications to Slack.
Overview
This flow demonstrates:
- HTTP webhook reception
- Data transformation
- Conditional routing
- External API integration (Slack)
- Error handling
Flow Architecture
┌─────────────┐
┌───►│ Critical │───┐
│ │ Channel │ │
┌─────────┐ ┌───────────┐ ┌──┴────┴─────────────┴───┴──┐
│ Webhook │───►│ Transform │───►│ Router │
│ Server │ │ Data │ │ (by severity) │
└─────────┘ └───────────┘ └──┬────┬─────────────┬───┬──┘
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌────────┴──┐
│ │ Warning │ │ Default │
│ │ Channel │ │ Channel │
│ └─────────┘ └───────────┘
│
▼
┌─────────┐
│ Slack │
│ API │
└─────────┘Node Configurations
1. HTTP Server (Webhook Receiver)
Component: http-module/server
Settings:
path: "/webhook/alerts"
methods: ["POST"]
hostnames: ["alerts.myapp.example.com"]
timeout: "10s"
enableCors: falsePurpose: Receives incoming webhook payloads from monitoring systems, CI/CD tools, or other services.
2. Transform Data
Component: common-module/modify
Input Edge Configuration:
data:
# Extract and normalize webhook payload
severity: "{{$.request.body.severity || $.request.body.level || 'info'}}"
title: "{{$.request.body.title || $.request.body.subject || 'Alert'}}"
message: "{{$.request.body.message || $.request.body.description || $.request.body.text}}"
source: "{{$.request.body.source || $.request.headers['X-Webhook-Source'] || 'unknown'}}"
timestamp: "{{RFC3339(now())}}"
raw: "{{$.request.body}}"
requestId: "{{$.request.requestId}}"Purpose: Normalizes different webhook formats into a consistent structure.
3. Conditional Router
Component: common-module/router
Settings:
rules:
- name: "critical_alerts"
field: "severity"
operator: "equals"
value: "critical"
outputPort: "route_a"
- name: "error_alerts"
field: "severity"
operator: "equals"
value: "error"
outputPort: "route_a"
- name: "warning_alerts"
field: "severity"
operator: "equals"
value: "warning"
outputPort: "route_b"
defaultPort: "default"
stopOnMatch: truePurpose: Routes alerts to different Slack channels based on severity.
4. Slack Message Formatter (Critical)
Component: common-module/modify
Input Edge Configuration (from route_a):
data:
channel: "#alerts-critical"
blocks:
- type: "header"
text:
type: "plain_text"
text: "🚨 {{upper($.severity)}}: {{$.title}}"
- type: "section"
text:
type: "mrkdwn"
text: "{{$.message}}"
- type: "context"
elements:
- type: "mrkdwn"
text: "Source: *{{$.source}}* | Time: {{$.timestamp}}"
requestId: "{{$.requestId}}"5. Slack Message Formatter (Warning)
Component: common-module/modify
Input Edge Configuration (from route_b):
data:
channel: "#alerts-warning"
blocks:
- type: "section"
text:
type: "mrkdwn"
text: "⚠️ *{{$.title}}*\n{{$.message}}"
- type: "context"
elements:
- type: "mrkdwn"
text: "Source: {{$.source}} | {{$.timestamp}}"
requestId: "{{$.requestId}}"6. Slack Message Formatter (Default)
Component: common-module/modify
Input Edge Configuration (from default):
data:
channel: "#alerts-general"
text: "ℹ️ [{{$.source}}] {{$.title}}: {{$.message}}"
requestId: "{{$.requestId}}"7. Slack API Client
Component: http-module/client
Settings:
baseUrl: "https://slack.com/api"
defaultHeaders:
Authorization: "Bearer {{$.secrets.slackToken}}"
Content-Type: "application/json"
timeout: "10s"
retryCount: 2
retryDelay: "1s"Input Edge Configuration:
data:
method: "POST"
url: "/chat.postMessage"
body:
channel: "{{$.channel}}"
blocks: "{{$.blocks}}"
text: "{{$.text}}"8. Response Handler
Component: common-module/modify
Sends response back to webhook caller:
Input Edge Configuration:
target:
node: http-server
port: response
data:
requestId: "{{$.requestId}}"
statusCode: 200
contentType: "application/json"
body: "{\"status\":\"accepted\",\"channel\":\"{{$.channel}}\"}"9. Error Handler
Component: common-module/modify
Handles Slack API errors:
Input Edge Configuration:
target:
node: http-server
port: response
data:
requestId: "{{$.requestId}}"
statusCode: 500
contentType: "application/json"
body: "{\"status\":\"error\",\"message\":\"{{$.error}}\"}"Complete Flow Definition
# TinyNode definitions for the flow
apiVersion: tinysystems.io/v1alpha1
kind: TinyNode
metadata:
name: webhook-server
namespace: alerts
spec:
component: http-module/server
edges:
- port: _settings
data:
path: "/webhook/alerts"
methods: ["POST"]
hostnames: ["alerts.myapp.example.com"]
---
apiVersion: tinysystems.io/v1alpha1
kind: TinyNode
metadata:
name: transform-webhook
namespace: alerts
spec:
component: common-module/modify
edges:
- port: input
node: webhook-server
nodePort: request
data:
severity: "{{$.body.severity || 'info'}}"
title: "{{$.body.title || 'Alert'}}"
message: "{{$.body.message}}"
source: "{{$.body.source || 'unknown'}}"
timestamp: "{{RFC3339(now())}}"
requestId: "{{$.requestId}}"
---
apiVersion: tinysystems.io/v1alpha1
kind: TinyNode
metadata:
name: severity-router
namespace: alerts
spec:
component: common-module/router
edges:
- port: _settings
data:
rules:
- name: "critical"
field: "severity"
operator: "equals"
value: "critical"
outputPort: "route_a"
- name: "warning"
field: "severity"
operator: "equals"
value: "warning"
outputPort: "route_b"
defaultPort: "default"
- port: input
node: transform-webhook
nodePort: output
---
# Additional nodes for Slack formatting and API calls...Testing the Flow
Sample Webhook Payloads
Critical Alert:
curl -X POST https://alerts.myapp.example.com/webhook/alerts \
-H "Content-Type: application/json" \
-d '{
"severity": "critical",
"title": "Database Connection Failed",
"message": "Unable to connect to primary database. Failover initiated.",
"source": "db-monitor"
}'Warning Alert:
curl -X POST https://alerts.myapp.example.com/webhook/alerts \
-H "Content-Type: application/json" \
-d '{
"severity": "warning",
"title": "High Memory Usage",
"message": "Memory usage at 85%. Consider scaling.",
"source": "metrics-collector"
}'Info Alert:
curl -X POST https://alerts.myapp.example.com/webhook/alerts \
-H "Content-Type: application/json" \
-d '{
"severity": "info",
"title": "Deployment Complete",
"message": "Version 2.3.1 deployed successfully",
"source": "ci-pipeline"
}'Expected Slack Output
Critical Alert (#alerts-critical):
🚨 CRITICAL: Database Connection Failed
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Unable to connect to primary database. Failover initiated.
Source: db-monitor | Time: 2024-01-15T10:30:00ZWarning Alert (#alerts-warning):
⚠️ High Memory Usage
Memory usage at 85%. Consider scaling.
Source: metrics-collector | 2024-01-15T10:30:00ZInfo Alert (#alerts-general):
ℹ️ [ci-pipeline] Deployment Complete: Version 2.3.1 deployed successfullyKey Patterns Used
1. Data Normalization
Handle multiple webhook formats:
severity: "{{$.body.severity || $.body.level || 'info'}}"
title: "{{$.body.title || $.body.subject || 'Alert'}}"2. Request-Response Correlation
Pass request ID through the flow:
requestId: "{{$.request.requestId}}"3. Conditional Formatting
Different message formats per severity:
# Critical: Rich blocks with header
blocks:
- type: "header"
text:
type: "plain_text"
text: "🚨 {{upper($.severity)}}"
# Info: Simple text
text: "ℹ️ [{{$.source}}] {{$.title}}"4. Error Response
Return errors to webhook caller:
statusCode: 500
body: "{\"status\":\"error\",\"message\":\"{{$.error}}\"}"Extending the Flow
Add Rate Limiting
Insert a rate limiter before Slack API:
component: common-module/rate-limiter
settings:
maxRequests: 10
windowSeconds: 60Add Deduplication
Prevent duplicate alerts:
component: common-module/dedup
settings:
keyExpression: "{{$.source}}-{{$.title}}"
ttlSeconds: 300Add Acknowledgment Tracking
Store alerts in database:
component: database-module/insert
data:
query: "INSERT INTO alerts (id, severity, title, source, created_at) VALUES ($1, $2, $3, $4, $5)"
parameters:
- "{{$.requestId}}"
- "{{$.severity}}"
- "{{$.title}}"
- "{{$.source}}"
- "{{$.timestamp}}"Monitoring
Track flow metrics:
- Webhook requests/minute: Monitor incoming traffic
- Slack API latency: Track external API performance
- Error rate: Alert on delivery failures
- Channel distribution: See which severity levels are most common