Skip to content

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:

yaml
path: "/webhook/alerts"
methods: ["POST"]
hostnames: ["alerts.myapp.example.com"]
timeout: "10s"
enableCors: false

Purpose: Receives incoming webhook payloads from monitoring systems, CI/CD tools, or other services.

2. Transform Data

Component: common-module/modify

Input Edge Configuration:

yaml
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:

yaml
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: true

Purpose: Routes alerts to different Slack channels based on severity.

4. Slack Message Formatter (Critical)

Component: common-module/modify

Input Edge Configuration (from route_a):

yaml
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):

yaml
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):

yaml
data:
  channel: "#alerts-general"
  text: "ℹ️ [{{$.source}}] {{$.title}}: {{$.message}}"
  requestId: "{{$.requestId}}"

7. Slack API Client

Component: http-module/client

Settings:

yaml
baseUrl: "https://slack.com/api"
defaultHeaders:
  Authorization: "Bearer {{$.secrets.slackToken}}"
  Content-Type: "application/json"
timeout: "10s"
retryCount: 2
retryDelay: "1s"

Input Edge Configuration:

yaml
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:

yaml
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:

yaml
target:
  node: http-server
  port: response
data:
  requestId: "{{$.requestId}}"
  statusCode: 500
  contentType: "application/json"
  body: "{\"status\":\"error\",\"message\":\"{{$.error}}\"}"

Complete Flow Definition

yaml
# 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:

bash
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:

bash
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:

bash
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:00Z

Warning Alert (#alerts-warning):

⚠️ High Memory Usage
Memory usage at 85%. Consider scaling.

Source: metrics-collector | 2024-01-15T10:30:00Z

Info Alert (#alerts-general):

ℹ️ [ci-pipeline] Deployment Complete: Version 2.3.1 deployed successfully

Key Patterns Used

1. Data Normalization

Handle multiple webhook formats:

yaml
severity: "{{$.body.severity || $.body.level || 'info'}}"
title: "{{$.body.title || $.body.subject || 'Alert'}}"

2. Request-Response Correlation

Pass request ID through the flow:

yaml
requestId: "{{$.request.requestId}}"

3. Conditional Formatting

Different message formats per severity:

yaml
# 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:

yaml
statusCode: 500
body: "{\"status\":\"error\",\"message\":\"{{$.error}}\"}"

Extending the Flow

Add Rate Limiting

Insert a rate limiter before Slack API:

yaml
component: common-module/rate-limiter
settings:
  maxRequests: 10
  windowSeconds: 60

Add Deduplication

Prevent duplicate alerts:

yaml
component: common-module/dedup
settings:
  keyExpression: "{{$.source}}-{{$.title}}"
  ttlSeconds: 300

Add Acknowledgment Tracking

Store alerts in database:

yaml
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

Build flow-based applications on Kubernetes