0

I tried building a sample SFMC custom activity that will do a post to the given input cloud page URL. App is deployed correctly in Heroku as well as setup is done correctly in SFMC. Journey is also configured correctly and the activity is added in journey and the cloud page URL is set up in the activity.

When the SFMC journey is executing , the contact is entering / evaluated and the journey is completed. But when I go to see the custom activity Activity Summary dashboard in SFMC journey it shows status as success , but status details as Status Code: 401 Status: Unauthorized. In SFMC journey log as well as given by support it shows a 401 error , but in heroku log I only see a call to config.json with a response code of 200 and nothing else. In config.json useJwt is set as true and JWT is also setup accordingly in Heroku. Can any one suggest what i am missing here or the reason of issue. Is it the execute in config.json giving 401 although JWT token is still set correctly. Any pointer will be appreciated.

Config.Json

{
    "workflowApiVersion": "1.1",
    "metaData": {
        "icon": "images/iconMedium.png",
        "iconSmall": "images/iconSmall.png",
        "category": "custom"
    },
    "type": "REST",
    "lang": {
        "en-US": {
         "name": "CloudPage POST",
          "description": "Sample Journey Builder Custom Activity",
          "step1Label": "Configure Activity"
        }
    },
    "arguments": {
        "execute": {
           "inArguments":[
                {"subscriberKey":"{{Contact.Key}}"}
            ],
          "outArguments": [],
          "url": "https://sfmc-journey-custom-activity.herokuapp.com/journeybuilder/execute",
           "verb": "POST",
            "body": "",
            "header": "",
            "format": "json",
            "useJwt": true,
            "timeout": 10000,
            "retryCount": 5,
            "retryDelay": 100
        }
    },
    "configurationArguments": {
         "applicationExtensionKey": "18a8f0f8-e048-4152-b2a5-a73adf1a4512",
      "save": {
        "url": "https://sfmc-journey-custom-activity.herokuapp.com/journeybuilder/save",
          "verb": "POST",
        "useJwt": true
       },
       "publish": {
        "url": "https://sfmc-journey-custom-activity.herokuapp.com/journeybuilder/publish",
           "verb": "POST",
        "useJwt": true
       },
      "stop": {
        "url": "https://sfmc-journey-custom-activity.herokuapp.com/journeybuilder/stop",
           "verb": "POST",
        "useJwt": true
      },
      "validate": {
        "url": "https://sfmc-journey-custom-activity.herokuapp.com/journeybuilder/validate",
        "verb": "POST",
        "useJwt": true
      }
    },
    "wizardSteps": [
        { "label": "Configure Activity", "key": "step1" }
    ],
    "userInterfaces": {
        "configModal": {
            "height": 640,
            "width": 900,
          "fullscreen": false
        }
    },
    "schema": {
        "arguments": {
            "execute": {
                "inArguments": [],
                "outArguments": []
            }
        }
    }
}

Index.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
    <meta http-equiv="x-ua-compatible" content="ie=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="css/styles.css">
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script type="text/javascript" src="js/require.js"></script>
    <script type="text/javascript">
        (function() {
            var config = {
                baseUrl: 'js'
            };

            var dependencies = [
                'customActivity'
            ];

            require(config, dependencies);
        })();
    </script>
</head>
<body>
    <div id="step1" class="step">
        <div class="slds-grid">

            <main class="slds-col">
                <div class="wrapper fadeInDown">
                    <div id="formContent">
                        <!-- Tabs Titles -->
                        <h2 class="active">Post Data To CloudPage</h2>

                        <!-- Icon -->
                        <div class="fadeIn first">
                            <img src="images/iconLarge.png" id="icon" alt="Icon" />
                        </div>

                        <!-- CloudPage Form -->
                        <form>
                            <input type="text" id="cpURL" class="fadeIn second" name="cpURL" placeholder="CloudPage URL">
                        </form>
                    </div>
                </div>
            </main>

        </div>

    </div>
</body>
</html>

customActivity.js

define([
    'postmonger'
], function(
    Postmonger
) {
    'use strict';

    var connection = new Postmonger.Session();
    var payload = {};
    $(window).ready(onRender);

    connection.on('initActivity', initialize);
    connection.on('clickedNext', save);

    connection.on("requestedTokens", onGetTokens);
    function onGetTokens(tokens) {
      console.log(tokens);
    }
    function onRender() {
        connection.trigger('ready');
        connection.trigger("requestTokens");
    }

    function initialize(data) {
        if (data) {
            payload = data;``
            var setcpURL = payload['arguments'].execute.inArguments[0].cloudpageURL;
            $('#cpURL').val(setcpURL);
        }
    }

    function save() {
        var cpURL = $('#cpURL').val();
        payload['arguments'].execute.inArguments = [{
            "subscriberKey": "{{Contact.Key}}",
            "cloudpageURL": cpURL
        }];
        payload['metaData'].isConfigured = true;
        console.log(payload);
        connection.trigger('updateActivity', payload);
    }
});

activity.js

'use strict';
var util = require('util');
var request = require('request');
// Deps
const Path = require('path');
const JWT = require(Path.join(__dirname, '..', 'lib', 'jwtDecoder.js'));
var util = require('util');
var http = require('https');

exports.logExecuteData = [];

function logData(req) {
  exports.logExecuteData.push({
    body: req.body,
    headers: req.headers,
    trailers: req.trailers,
    method: req.method,
    url: req.url,
    params: req.params,
    query: req.query,
    route: req.route,
    cookies: req.cookies,
    ip: req.ip,
    path: req.path,
    host: req.host,
    fresh: req.fresh,
    stale: req.stale,
    protocol: req.protocol,
    secure: req.secure,
    originalUrl: req.originalUrl
  });
  console.log("body: " + util.inspect(req.body));
  console.log("headers: " + req.headers);
  console.log("trailers: " + req.trailers);
  console.log("method: " + req.method);
  console.log("url: " + req.url);
  console.log("params: " + util.inspect(req.params));
  console.log("query: " + util.inspect(req.query));
  console.log("route: " + req.route);
  console.log("cookies: " + req.cookies);
  console.log("ip: " + req.ip);
  console.log("path: " + req.path);
  console.log("host: " + req.host);
  console.log("fresh: " + req.fresh);
  console.log("stale: " + req.stale);
  console.log("protocol: " + req.protocol);
  console.log("secure: " + req.secure);
  console.log("originalUrl: " + req.originalUrl);
}

/*
 * POST Handler for / route of Activity (this is the edit route).
 */
exports.edit = function(req, res) {
  // Data from the req and put it in an array accessible to the main app.
  //console.log( req.body );
  logData(req);
  res.send(200, 'Edit');
};

/*
 * POST Handler for /save/ route of Activity.
 */
exports.save = function(req, res) {
  // Data from the req and put it in an array accessible to the main app.
  //console.log( req.body );
  logData(req);
  res.send(200, 'Save');
};

/*
 * POST Handler for /execute/ route of Activity.
 */
exports.execute = function(req, res) {
  // example on how to decode JWT
  JWT(req.body, process.env.jwtSecret, (err, decoded) => {

    // verification error -> unauthorized request
    if (err) {
            console.error(err);
            return res.status(401).end();
    }
    if (decoded && decoded.inArguments && decoded.inArguments.length > 0) {
      var decodedArgs = decoded.inArguments[0];
      var cpURL = decodedArgs.cloudpageURL;
      var subKey = decodedArgs.subscriberKey;
      var http = require('http')
      var cpPostBody = JSON.stringify({
          "activityData": {
            "SubscriberKey": subKey,
            "CloudPageURL": cpURL,
            "decodedArgs": decodedArgs
          }
      });
      request.post({
        headers: {
          "Content-Type": "application/json"
        },
        url: cpURL,
        body: cpPostBody
      }, function(error, response, body) {
          if (error) {
            console.log(error);
        };
      });
      logData(req);
      res.send(200, 'Execute');
    } else {
      console.error('inArguments invalid.');
      return res.status(400).end();
    }
  });
};


/*
 * POST Handler for /publish/ route of Activity.
 */
exports.publish = function(req, res) {
  // Data from the req and put it in an array accessible to the main app.
  //console.log( req.body );
  logData(req);
  res.send(200, 'Publish');
};

/*
 * POST Handler for /validate/ route of Activity.
 */
exports.validate = function(req, res) {
  // Data from the req and put it in an array accessible to the main app.
  //console.log( req.body );
  logData(req);
  res.send(200, 'Validate');
};

JB Error Pic

enter image description here

1
  • Are you sure that you have set an environment variable in Heroku successfully and that it maps to the jwtSecret you have in SFMC (and matches what you've declared in your execute route)? Aug 29, 2022 at 1:06

1 Answer 1

1

I have figured it out.

Seems when a change is made in config.json , a new journey have to be setup using that custom activity. And post that it worked fine.

Earlier i was only creating new version of the same journey post the change.

Any explanation why a new new journey always have to be setup post any change in config.json , is that due to some internal mapping of interaction / journey id?

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .