Skip to main content

Slack

Server Source code Package

Send walkerOS events to Slack as formatted messages. Supports both Incoming Webhooks (simple, single-channel) and the Web API (multi-channel routing, threading, DMs, ephemeral messages, Block Kit).

Where this fits

Slack is a server destination in the walkerOS flow:

Turns events into Slack messages for real-time alerts, sales notifications, moderation queues, or incident routing, without coupling your product code to Slack.

Installation

npm install @walkeros/server-destination-slack
import { startFlow } from '@walkeros/collector';
import { destinationSlack } from '@walkeros/server-destination-slack';

await startFlow({
destinations: {
  slack: {
    code: destinationSlack,
    config: {
      settings: {
        token: 'xoxb-your-bot-token',
        channel: '#notifications',
      },
    },
  },
},
});

Configuration

This destination uses the standard destination config wrapper (consent, data, env, id, ...). For the shared fields see destination configuration. Package-specific fields live under config.settings and are listed below.

Settings

PropertyTypeDescriptionMore
tokenstringSlack Bot token (xoxb-...). Enables Web API mode. Mutually exclusive with webhookUrl.
webhookUrlstringIncoming Webhook URL. Enables webhook mode. Mutually exclusive with token.
channelstringDefault Slack channel ID or name (e.g. "#alerts" or "C024BE91L"). Required for Web API mode unless every rule supplies one. Ignored in webhook mode.
textstringDefault text template. Supports `${data.field}` interpolation against the walkerOS event.
blocksArray<object>Default Block Kit blocks applied when no mapping override is set.
includeHeaderbooleanAuto-add an event-name header block when generating default blocks. Default: true.
unfurlLinksbooleanEnable link unfurling. Default: false (cleaner for automated alerts).
unfurlMediabooleanEnable media unfurling. Default: false.
mrkdwnbooleanUse mrkdwn formatting in text. Default: true.
threadTsstringStatic thread_ts for replies (rarely set at destination level).
retryConfigRetry policy passed to WebClient. Default: "default".

Mapping

Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

PropertyTypeDescriptionMore
channelstringOverride the destination channel for this rule. Web API mode only -- ignored in webhook mode.
textstringOverride the text template for this rule. Supports `${data.field}` interpolation.
blocksArray<object>Override Block Kit blocks for this rule.
threadTsstringthread_ts for posting as a reply in a thread.
replyBroadcastbooleanAlso broadcast the threaded reply back to the channel.
ephemeralbooleanSend via chat.postEphemeral. Requires `user`.
userstringSlack user ID for ephemeral or DM delivery.
dmbooleanSend as DM via conversations.open + chat.postMessage. Requires `user`.

Examples

Default blocks

With no custom text the destination auto-generates a Block Kit message from event data and source.

Event
{
  "name": "lead submit",
  "data": {
    "name": "Acme",
    "email": "sales@acme.test"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "b8e4e9f739d0037a",
  "trigger": "test",
  "entity": "lead",
  "action": "submit",
  "timestamp": 1700000600,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "crm",
    "platform": "server"
  }
}
Mapping
{
  "settings": {
    "channel": "#growth"
  }
}
Out
slackClient.chat.postMessage({
  "channel": "#growth",
  "text": "lead submit",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "lead submit"
      }
    },
    {
      "type": "section",
      "fields": [
        {
          "type": "mrkdwn",
          "text": "*name:*\nAcme"
        },
        {
          "type": "mrkdwn",
          "text": "*email:*\nsales@acme.test"
        }
      ]
    },
    {
      "type": "context",
      "elements": [
        {
          "type": "mrkdwn",
          "text": "Source: crm"
        }
      ]
    }
  ],
  "unfurl_links": false,
  "unfurl_media": false,
  "mrkdwn": true
})

Webhook deploy

Without a token the destination posts to an incoming Slack webhook URL with the rendered message body.

Event
{
  "name": "deploy complete",
  "data": {
    "version": "1.4.2",
    "environment": "prod"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "760f2d3325a05a3c",
  "trigger": "test",
  "entity": "deploy",
  "action": "complete",
  "timestamp": 1700000700,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "settings": {
    "text": ":rocket: Deployment complete: ${data.version} to ${data.environment}"
  }
}
Out
sendServer("https://hooks.slack.com/services/T00/B00/xxx", "{\"text\":\":rocket: Deployment complete: 1.4.2 to prod\",\"unfurl_links\":false,\"unfurl_media\":false,\"mrkdwn\":true}")

Ephemeral message

A quota warning posts an ephemeral Slack message visible only to a target admin user.

Event
{
  "name": "quota warning",
  "data": {
    "remaining": 5
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "4f09c1758f39f293",
  "trigger": "test",
  "entity": "quota",
  "action": "warning",
  "timestamp": 1700000500,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "settings": {
    "channel": "#admin",
    "ephemeral": true,
    "user": "U-ADMIN-1",
    "text": "Heads up: ${data.remaining} requests remaining"
  }
}
Out
slackClient.chat.postEphemeral({
  "channel": "#admin",
  "text": "Heads up: 5 requests remaining",
  "unfurl_links": false,
  "unfurl_media": false,
  "mrkdwn": true,
  "user": "U-ADMIN-1"
})

Error alert

An error event posts a critical alert to an engineering channel using a mapping-level channel override.

Event
{
  "name": "error occur",
  "data": {
    "message": "Payment gateway timeout",
    "code": "PGW_TIMEOUT",
    "severity": "critical"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "0b3cc1c5fbc7b5e8",
  "trigger": "test",
  "entity": "error",
  "action": "occur",
  "timestamp": 1700000200,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "settings": {
    "channel": "#engineering-alerts",
    "text": ":rotating_light: Error: ${data.message}"
  }
}
Out
slackClient.chat.postMessage({
  "channel": "#engineering-alerts",
  "text": ":rotating_light: Error: Payment gateway timeout",
  "unfurl_links": false,
  "unfurl_media": false,
  "mrkdwn": true
})

Purchase alert

A completed order posts a templated message to a sales channel via the Slack Web API.

Event
{
  "name": "order complete",
  "data": {
    "id": "ORD-500",
    "total": 299.99,
    "currency": "EUR",
    "product": "Pro Plan"
  },
  "context": {
    "shopping": [
      "complete",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "buyer-42"
  },
  "nested": [
    {
      "entity": "product",
      "data": {
        "id": "ers",
        "name": "Everyday Ruck Snack",
        "color": "black",
        "size": "l",
        "price": 420
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    },
    {
      "entity": "product",
      "data": {
        "id": "cc",
        "name": "Cool Cap",
        "size": "one size",
        "price": 42
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    },
    {
      "entity": "gift",
      "data": {
        "name": "Surprise"
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "1167143687e78036",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000100,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "settings": {
    "channel": "#sales",
    "text": ":moneybag: New order: ${data.id} - ${data.total} ${data.currency}"
  }
}
Out
slackClient.chat.postMessage({
  "channel": "#sales",
  "text": ":moneybag: New order: ORD-500 - 299.99 EUR",
  "unfurl_links": false,
  "unfurl_media": false,
  "mrkdwn": true
})

Threaded reply

A checkout step posts as a threaded reply in Slack via thread_ts with broadcast to the channel.

Event
{
  "name": "checkout step",
  "data": {
    "step": "payment",
    "sessionTs": "1700000000.000050"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "283aa92727baa7f3",
  "trigger": "test",
  "entity": "checkout",
  "action": "step",
  "timestamp": 1700000400,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "settings": {
    "channel": "#sales",
    "text": "Checkout step: ${data.step}",
    "threadTs": "1700000000.000050",
    "replyBroadcast": true
  }
}
Out
slackClient.chat.postMessage({
  "channel": "#sales",
  "text": "Checkout step: payment",
  "unfurl_links": false,
  "unfurl_media": false,
  "mrkdwn": true,
  "thread_ts": "1700000000.000050",
  "reply_broadcast": true
})

Welcome DM

A user signup opens a Slack DM channel and posts a welcome message with the selected plan.

Event
{
  "name": "user signup",
  "data": {
    "plan": "enterprise"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "U-NEW-USER"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "9ca5d22d318be388",
  "trigger": "test",
  "entity": "user",
  "action": "signup",
  "timestamp": 1700000300,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "settings": {
    "dm": true,
    "user": "U-NEW-USER",
    "text": ":wave: Welcome aboard! You signed up for the ${data.plan} plan."
  }
}
Out
slackClient.conversations.open({
  "users": "U-NEW-USER"
});

slackClient.chat.postMessage({
  "channel": "D-MOCK-DM",
  "text": ":wave: Welcome aboard! You signed up for the enterprise plan.",
  "unfurl_links": false,
  "unfurl_media": false,
  "mrkdwn": true
})

Webhook vs Web API modes

Pick exactly one auth method per destination. Each mode trades capability for simplicity.

CapabilityIncoming Webhook (webhookUrl)Web API (token)
SetupPaste a URL, doneCreate a Slack app, install bot, manage scopes
ChannelsFixed to the webhook's channelAny channel the bot is in, routable per rule
ThreadingNot supportedthreadTs + replyBroadcast
Direct messagesNot supporteddm: true + user
Ephemeral messagesNot supportedephemeral: true
RetriesPlain HTTPSDK-managed (retryConfig)
Block KitYesYes

Use webhooks for a single firehose channel and minimal setup. Use the Web API whenever you need routing, threading, DMs, or ephemeral messages.

Channel routing

With Web API mode, route events to different channels using nested entity > action mapping. Each rule can override channel, text, blocks, and threading behavior.

"destinations": {
"slack": {
  "package": "@walkeros/server-destination-slack",
  "import": "destinationSlack",
  "config": {
    "settings": {
      "token": "$env.SLACK_BOT_TOKEN",
      "channel": "#events"
    },
    "mapping": {
      "order": {
        "complete": {
          "settings": {
            "channel": "#sales",
            "text": "New order ${data.id}: ${data.total} ${data.currency}"
          }
        }
      },
      "error": {
        "report": {
          "settings": {
            "channel": "#incidents",
            "threadTs": "data.incidentTs"
          }
        }
      },
      "user": {
        "signup": {
          "settings": {
            "channel": "#growth"
          }
        }
      }
    }
  }
}
}

Events that don't match any rule fall back to the default channel in settings.

Message formatting

Messages support two formatting styles, set at destination level or per rule:

  • Text templates: ${data.field} placeholders interpolate against the walkerOS event. Good for terse alerts.
  • Block Kit: structured layouts via Slack's Block Kit (headers, sections, buttons, context). Use the blocks setting when you need rich formatting.

When neither text nor blocks is provided, the destination generates a default message with an event-name header (toggle via includeHeader).

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)