What you'll have at the end.
A custom activity registered in Marketing Cloud, published into Journey Builder, with a Node.js service hosting the four lifecycle endpoints. After this guide your activity will show up in the canvas palette and respond to test contacts you push through it.
For the system-level architecture, see the full SFMC Custom Activity Tutorial. This guide is the creation procedure.
What you need before starting.
- A Marketing Cloud account with Administrator access
- Journey Builder enabled in your Business Unit
- A public HTTPS endpoint (or a tunnel like ngrok for dev)
- Node.js 18+ and a package manager (npm / pnpm / bun)
- Basic familiarity with Express and JSON
- An idea of what your activity should do at runtime
Create the Installed Package.
The Installed Package is the SFMC-side container for your activity. It owns the JWT signing secret you'll use to verify runtime calls.
- 01Open Setup → Platform Tools → Apps → Installed Packages
- 02Click New, give it a clear name (e.g. "Slack Notifier")
- 03Save — this generates the JWT signing secret
- 04Copy the JWT secret into your backend's environment variables as SFMC_JWT_SECRET

Add the Journey Builder Activity component.
Inside your Installed Package, click Add Component → Journey Builder Activity. Point the endpoint base URL at where your Node.js service will be hosted.
Configure the lifecycle endpoints.
The activity's config.json is the contract between Marketing Cloud and your service. It declares the URLs, the runtime arguments, and the JWT signing requirement.
{
"workflowApiVersion": "1.1",
"metaData": { "icon": "images/slack.png", "category": "messaging" },
"type": "REST",
"arguments": { "execute": {
"url": "https://your-host.com/execute",
"verb": "POST",
"useJwt": true
} },
"configurationArguments": {
"save": { "url": "https://your-host.com/save" },
"validate": { "url": "https://your-host.com/validate" },
"publish": { "url": "https://your-host.com/publish" }
}
}Build the Node.js backend.
A minimal Express scaffold with all four endpoints wired. Drop this file in, install express and jsonwebtoken, and you're ready to register the activity.
import express from "express";
import jwt from "jsonwebtoken";
const app = express();
app.use(express.json());
app.use(express.text({ type: "application/jwt" }));
// Configuration endpoints — open during canvas editing
app.post("/save", (req, res) => res.json({ status: "saved" }));
app.post("/validate", (req, res) => res.json({ status: "ok" }));
app.post("/publish", (req, res) => res.json({ status: "published" }));
// Runtime endpoint — JWT-signed
app.post("/execute", (req, res) => {
const body = jwt.verify(req.body, process.env.SFMC_JWT_SECRET);
// body.inArguments[0] holds your config + contact data
doSideEffect(body.inArguments[0]);
res.json({ status: "executed" });
});
app.listen(3000);/execute call with the activityInstanceId, the contact key, and a hash of the inArguments. Journey Builder retries are loud, but you'll wish you had structured logs the first time something silently fails.Deploy and test the journey end-to-end.
Deploy the Node.js service. Then go back into Journey Builder, build a small test journey with one entry source (a Data Extension with two test contacts) and your new activity, and run it.
- 01Deploy the backend to Vercel / Render / your platform of choice
- 02Confirm /save, /validate, /publish, /execute respond 200 to a curl test
- 03Refresh Journey Builder — your activity now appears in the palette
- 04Build a small journey with two test contacts and activate it
- 05Watch your service logs and confirm /execute fires per contact

Where to go from here.
Full SFMC Custom Activity Tutorial
End-to-end system view — architecture, JWT, Data Extensions, Slack integration.
Read article ConceptsHow custom activities actually work
Architecture deep dive — lifecycle, runtime flow, retry behavior.
Read article IntegrationCustom activity for Slack
Slack-specific build — webhook setup, templating, error handling.
Read article