Integration · Slack + SFMC

SFMC Journey Builder Custom Activity for Slack Integration.

Push real-time Slack notifications from inside Salesforce Marketing Cloud journeys — without a middleware tool. Webhook setup, Block Kit templating, retry behavior, and the production patterns that keep alerts reliable when traffic spikes.

Slack · SFMCRead time · 10 minUpdated · May 2026By Lalit Chaudhari
01 — Introduction

What you'll build.

A Journey Builder custom activity that, every time a contact reaches it, posts a templated Slack message to a configured channel. Marketers configure the channel, the template, and the merge fields directly inside Journey Builder — no engineering ticket per campaign.

Step 01
Contact reaches step
Step 02
/execute called
JWT POST
Step 03
Render template
Merge fields → Block Kit
Step 04
POST to Slack webhook
Step 05
Channel receives alert
Activities · Messagesscreenshot
Salesforce Marketing Cloud Journey Builder canvas with the Activities palette open under Messages, showing Email, In-App Message, Inbox, Push Notification, Slack Message, SMS, and WhatsApp — the Slack Message custom activity sitting alongside native messaging steps.
The end state — the Slack Message custom activity registered in the Journey Builder palette under Messages, ready for marketers to drag onto any journey canvas.
02 — Why

Why a Slack custom activity beats middleware.

  • No paid hop — the message goes Marketing Cloud → your service → Slack
  • Sub-2-second alerts, vs. middleware schedulers that poll on a delay
  • Marketers configure each step inline, not in a separate tool
  • The activity is reusable across every journey in the BU
Activity configscreenshot
Journey Builder canvas with an Activity Summary drawer open on the left — Activity Name, Description, Message Definition, and Message Configuration fields visible — running alongside an Abandoned Cart journey on the right with Decision Split and downstream steps.
Activity configuration happens inline — marketers fill the drawer without leaving the Journey Builder canvas. The same pattern hosts your Slack activity's channel + template fields.
Step 01

Create the Slack Incoming Webhook.

  1. 01Go to api.slack.com/apps → Create New App → From scratch
  2. 02Pick a workspace, name the app (e.g. "SFMC Notifier")
  3. 03Enable Incoming Webhooks under Features
  4. 04Add a New Webhook to Workspace, select the destination channel
  5. 05Copy the webhook URL — store it in your secret manager
Security · don't expose the webhook
A Slack webhook URL is effectively a secret that can post to your channel. Never store it in the activity's inArguments directly — anyone with read access to the journey would see it. Use an alias resolved server-side.
Step 02

Build the Node.js backend.

Use the same Express scaffold as any custom activity — four endpoints, JWT verification on /execute. The Slack-specific work happens inside the execute handler.

See the creation walkthrough for the base server setup.

Step 03

Handle the execute endpoint.

The /execute handler verifies the JWT, resolves the webhook alias to a real URL, renders the message, and posts to Slack.

execute-slack.jsnode
import jwt from "jsonwebtoken";
import { resolveWebhook } from "./secrets.js";
import { renderBlocks } from "./templates.js";

export async function executeHandler(req, res) {
  const body = jwt.verify(req.body, process.env.SFMC_JWT_SECRET);
  const { channelAlias, template, ...contact } = body.inArguments[0];

  const webhookUrl = await resolveWebhook(channelAlias);
  const blocks = renderBlocks(template, contact);

  const resp = await fetch(webhookUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ blocks }),
  });

  if (!resp.ok) return res.status(502).json({ status: "slack_failed" });
  return res.json({ status: "executed" });
}
Step 04

Send a Slack message.

The Slack webhook accepts either a plain text field or a structured blocks array (Block Kit). For real product alerts, blocks beat plain text every time.

slack payloadjson
{
  "blocks": [
    { "type": "header", "text": { "type": "plain_text", "text": "New high-value lead" } },
    { "type": "section", "fields": [
      { "type": "mrkdwn", "text": "*Name:*\nJane Doe" },
      { "type": "mrkdwn", "text": "*Score:*\n92" }
    ] }
  ]
}
Step 05

Handle failures gracefully.

Slack can rate-limit. Channels can be archived. Webhooks can be revoked. Your handler needs a clear policy for each — and it should never silently drop a contact.

Slack rate-limited (429)

Respect the Retry-After header. Either return non-2xx so JB retries, or queue the message and return 200.

Channel archived / deleted

Slack returns channel_not_found. Log the failure, alert ops, and skip the contact rather than retrying forever.

Webhook revoked

Slack returns 410 Gone or 404. The webhook URL is dead — surface this clearly so marketers reissue it.

Network timeout

Wrap the fetch with a 1.5s timeout. On timeout, queue and return 200 — never block Journey Builder waiting.

Pattern · Dead-letter queue
For high-volume journeys, push the Slack send onto a queue from /execute and return 200 immediately. The queue worker handles retries and surfaces hard failures into a dead-letter store ops can review — the journey never stalls.
Templating

Block Kit templating with merge fields.

The marketer-facing template should be safe but expressive. A simple {{firstName}}-style replacement on top of a stored Block Kit JSON gets you most of the way without exposing arbitrary code.

templates.jsnode
// Replace {{token}} placeholders with values from contact
export function renderBlocks(templateJson, contact) {
  const rendered = templateJson.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) =>
    contact[key] ?? ""
  );
  return JSON.parse(rendered);
}
FAQ

Frequently asked questions.

Can I use Slack's Web API instead of an Incoming Webhook?
Yes — chat.postMessage gives you more control (threading, dynamic channels, scheduled sends) but requires a bot token with the right scopes. Incoming Webhooks are simpler when each Journey Builder step posts to a fixed channel.
How do I keep Slack webhook URLs out of the activity config?
Store webhook URLs in your backend's secret store keyed by an opaque alias (e.g. "sales-alerts"). The activity config saves only the alias; the backend resolves it to the actual URL at runtime so marketers never see the secret.
What happens if Slack returns rate-limit errors?
Slack returns 429 with a Retry-After header. Catch this, return a non-2xx from /execute (or queue the message), and let Journey Builder's retry policy back off. Don't fail the contact — log it and surface the error to ops.
Can I send Slack messages with rich formatting from a Journey Builder custom activity?
Yes — Slack's Block Kit lets you compose structured layouts. Render the Block Kit JSON inside /execute using the contact's merge fields and POST it to the webhook URL.

Need a Slack-SFMC integration shipped?

I've built and shipped this exact integration for production marketing teams. If you'd rather not build it from scratch, let's talk.

Start a conversation