API / SFMC / SSJS / WSProxy July 28, 2020
This is the fifth part in an ongoing series, please visit here to read the previous article. This article is intended to give a general overview on SFMC SSJS WSProxy and its uses and benefits.
What is WSProxy?
WS stands for WebSocket which, in a nutshell, provides a way of creating web applications that support real-time bidirectional communication with clients and servers. The WebSocket is like the satellite system used for cell phone service providers in that it is the main structure/service that allows the communication between client and server inside a specific format.
Proxy stands for a proxy server that acts as an intermediary between the client and another service. Imagine the proxy server to be the customer service department at a company that gets your complaint and then contacts the corresponding department to find the resolution you need. They then take the response and bring it back to you.
WSProxy in SFMC is a script object that is native to the platform and allows for communication with most available SOAP objects through platform authentication. It utilizes SSJS and JSON syntax to make these calls via the platform inside of Script Activities or CloudPages. WSProxy will not work in a messaging environment or outside of the SFMC platform.
Why Use WSProxy?
It is simpler to use than most of the legacy in-platform methods that existed beforehand (AMPScript and SSJS API functions), especially if you are familiar with JavaScript and JSON. WSProxy reduces overhead and increases the speed of your API calls, while maintaining the same ‘internal’ security level. The security is maintained as it is being authenticated via the platform (no need to include a user/pass or authentication token in the calls). With it being in-platform, it removes the need to know SOAP XML structuring and syntax.
WSProxy is much easier to maintain than the other in-platform solutions as it reduces the code required and is presented in a much more readable structure via JSON syntax. It also greatly reduces the amount of processing required to make the call with the reduced overhead.
What do I need to use WSProxy?
To utilize WSProxy, you will need:
- Background on the SOAP Objects available in SFMC
- Understanding of the different Method types and associated parameters for SOAP API
- Basic understanding on how APIs work
- Basic understanding of SSJS and JSON syntax and structures
- SSJS Looping, JSON parsing and interaction with AMPScript
I would highly recommend reading the documentation on SOAP API for SFMC concentrating on the Objects and Methods section before attempting to do anything with WSProxy.
I would also recommend learning more about what exactly JSON objects and arrays are and how they work prior to jumping into WSProxy. This article gives good insight into what JSON is and how it is used.
If you already have experience with SOAP XML already, this article may help fill in some holes in understanding when translating between the SOAP XML and JSON structures.
How do I use WSProxy?
There are some universal requirements to utilizing WSProxy, including opening the proxy object. To do this you would need to define a variable to contain the object, the below sample code would need to be placed prior to any WSProxy use:
var prox = new Script.Util.WSProxy();
This will set the variable prox to include the WSProxy object that you can use to interact with the SFMC SOAP Objects. This is only needed to be declared once and can be used for all future calls. You do not need to declare a new WSProxy object for each call.
Below is a list of methods and capabilities of WSProxy. I am sharing very generic SSJS functions for each method that you can use as a boilerplate for your own needs.
**As a note, I have not found a working example of a Config Method for SFMC in SOAP API or in WSProxy. The example in the docs returns an error when I try to use it in any environment. If you have an example I would love to share it, but until then what is listed is literally just a guess based off the documentation and incorrect examples.
Impersonation
This method allows you to switch between contexts from the default MID and/or users. The cool part about this is that it is applied directly to the WSProxy object, so you do not need to declare it for each usage. As well, within the same object, you can switch the targeted environment multiple times for multiple calls. But note, you will need to clear/reset the credentials set before switching environments again as most actions will only use the first one set and will not recognize anything set after the first one.
This method will only impersonate from top down. Meaning that a parent account can impersonate a child, but a child account cannot impersonate a parent.
Example of code to assign impersonation:
prox.setClientId({"ID": 1234, "UserID": 1234});
ID: This is the Member ID (MID) of your Business Unit (BU) that you want targeted as the environment for the Proxy Call. This can be found in many places, but the easiest way to grab it is from the UI. You would hover over the name of your current BU and then on the right side of the dropdown to switch BUs you will see the MID written out.
UserID: This is the internal ID number of the user you wish to impersonate. This number can be gathered by SOAP (ID field in AccountUser object) or by REST (id field from GET /platform/v1/accounts/{{et_mid}}/users
endpoint). This capability is limited in use. There are use-cases out there I am sure, but they seem to be very niche as I have not come across a good one yet.
To note, you only need to include one parameter inside this object, you do not need to include both. So you can do just an ID {"ID":1234}
or just a UserID {"UserID": 1234}
in the setClientId object.
Reset Impersonation:
When you want to switch to a new environment or want to return to the default, you would use this reset command to remove all existing declared context and return it to default.
Example of reset code:
prox.resetClientIds();
Delete Method
This method is used to remove a single item or multiple items inside of a specific object. This method can be called in two ways: deleteItem
and deleteBatch
.
deleteItem – This is used to delete a single item inside the specified SOAP Object. It will only require a single JSON object specifying the item you want removed.
deleteBatch – This is used to delete multiple items inside the specified SOAP Object. It will require a JSON array of objects specifying each item you want removed.
Delete has two required parameters and a single optional one in it:
- SOAP Object you want to target
- Filter to specify the item you wish to target. This can be an array or an object
- (Optional) Any properties you wish to pass in for the call.
//Deletes specific object from identified SOAP Object function deleteGeneric(soapObjName,filterObj,mid) { //example soapObjName: "DataExtension" //example filterObj: { "CustomerKey":customerKey } if(mid) { prox.resetClientIds(); //reset previous settings // Set ClientID prox.setClientId({ "ID": mid }); } var batch = isArray(contentJSON); if(batch) { var res = prox.deleteBatch(soapObjName,filterObj); } else { var res = prox.deleteItem(soapObjName,filterObj); } function isArray(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; return res; }
Note: You will notice in my function that I include a nested function isArray(arg)
to check if the content passed inside of the function is an array or an object. The purpose of this is to allow you to use the same function regardless if you want to delete a single item or delete a batch of items. It will be correctly assigned based on the type of the JSON passed in.
Describe Method
The Describe method is used to return information on the structure of the SOAP API object. It will return all available properties related to that object along with related information for those properties.
The only required entry for this method is the SOAP Object name. No other values need to be passed to make this work.
//retrieves the name of all retrievable fields inside obj function describeGeneric(soapObjName) { //example soapObjName: "DataExtension" var req = prox.describe(soapObjName); return req; }
Retrieve Method
The Retrieve method is designed to return multiple items (but can be a single item if filtered that way) with a maximum of 2500 records. This maximum is true for all SOAP API retrieves and is not a WSProxy limit.
To get around this, you will need to utilize the getNextBatch
function. I have included this inside of my example function below. You can also see an example in the offical docs here.
There are five possible parameters to be passed through in a Retrieve request. Only the first two are required for basic retrieves (with no specificity), the rest are only needed for more advanced Retrieve requests.
- Soap Object you want to target
- Array of column names that you want values returned from retrieve
- Filter object used to specify which items to return
- RetrieveOptions properties you want on request
- Any remaining Properites to set on your request (includes: QueryAllAccounts, RepeatLastResult,RepeatAllSinceLastBatch, etc)
As a note, even if the column name is not listed in the array you provided for the second parameter, it will appear inside the returned array. It will just have the ‘default’ or null value assigned to it.
This is just the way that WSProxy is built. So do not be alarmed when you view your response and see unnamed columns inside your results. Also keep this in mind that if it is showing null in your results, it could just not be listed in your second parameter and not actually null.
Be aware that not all properties of a SOAP Object are retrievable. Even if the property is listed in the SOAP Object documentation, it may not be something you can retrieve.
My recommendation is to use the Describe Method on the object to get the details on properties available. To this extent, you can utilize a describe WSProxy call to retrieve and create an array of all the retrievable properties of an object – so you do not need to list them manually.
//Retrieves object(s) from identified SOAP Object - optional filter function retrieveGeneric(soapObjName,cols,filter,opts,props,mid,reqID) { //example soapObjName: "DataExtension" //example cols: ["AccountUserID","ActiveFlag","ChallengeAnswer","ChallengePhrase"]; //example filter: { Property: "CustomerKey", SimpleOperator: "equals", Value: sourceDE }; //example opts: {BatchSize: 25} //example props: { QueryAllAccounts: true } //reqID is the id used to pull in next batch if returns more than 2500 max per call if(mid) { prox.resetClientIds(); //reset previous settings // Set ClientID prox.setClientId({ "ID": mid }); } if (reqID == null) { var res = prox.retrieve(soapObjName,cols,filter,opts,props); } else { var res = prox.getNextBatch(soapObjName,reqID); } return res; }
Example of how to use the generic function above to account for getNextBatch
:
var prox = new Script.Util.WSProxy(), reqID, moreData = true; // my script to set the values for function while(moreData) { moreData = false; var data = retrieveGeneric(soapObjName,cols,filter,opts,props,mid,reqID) if(data != null) { moreData = data.HasMoreRows; reqID = data.RequestID; //my script to interact with results } }
Create Method
This method is actually very similar to the Delete Method shown above – but there is a big difference in what information is passed inside that structure.
The Create method is used to create a single item or multiple items inside of a specific object. The Create method can be called in two ways: createItem
and createBatch
.
createItem – This is used to create a single item inside the specified SOAP Object. It will only require a single JSON object specifying the item you want created.
createBatch – This is used to create multiple items inside the specified SOAP Object. It will require a JSON array of objects specifying each item you want created.
Create has two required parameters and a single optional one in it:
- SOAP Object you want to target
- This can be an array or an object that is used to hold the fields and values to set on the object when created.
- (Optional) Any properties you wish to pass in for the call. Check out a list of the options here
//Creates new object(s) inside of the identified SOAP Object function createGeneric(soapObjName, contentJSON, props, mid) { //example soapObjName: "DataExtension" //example contentJSON Object: { "CustomerKey": name, "Name": name, "Fields": fields }; //example contentJSON Array: [{ "CustomerKey": name, "Name": name, "Fields": fields },{ "CustomerKey": name, "Name": name, "Fields": fields }] //example props: {SaveOptions:[{'PropertyName':'*',SaveAction:'UpdateAdd'}]} if(mid) { prox.resetClientIds(); //reset previous settings // Set ClientID prox.setClientId({ "ID": mid }); } var batch = isArray(contentJSON); if(batch) { var res = prox.createBatch(soapObjName,contentJSON); } else { var res = prox.createItem(soapObjName,contentJSON); } function isArray(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; return res; }
This also includes the isArray function to ensure it correctly assigns Batch or Item to your WSProxy call. See ‘Delete Method‘ for more info on this process.
Update Method
This method is very similar to both the Create and Delete methods shown above. The information passed into this structure would be lower than create as it should only include associated keys and any values you want to change in the item.
The Update method is used to change the values in a single item or multiple items inside of a specific object. The Update method can be called in two ways: updateItem
and updateBatch
.
updateItem – This is used to change a single item inside the specified SOAP Object. It will only require a single JSON object specifying the item you want adjusted.
updateBatch – This is used to change multiple items inside the specified SOAP Object. It will require a JSON array of objects specifying each item you want adjusted.
Update has two required parameters and a single optional one in it:
- SOAP Object you want to target
- This can be an array or an object that is used to hold the keys and the fields and values to change on the item.
- (Optional) Any properties you wish to pass in for the call. Check out a list of the options here
//Updates object(s) that exist inside the identified SOAP Object function updateGeneric(soapObjName,contentJSON,opts,mid) { //example opts: {SaveOptions:[{'PropertyName':'*',SaveAction:'UpdateAdd'}]} //example contentJSON Object: { CustomerKey: custKey, Properties: [{ Name: 'SubscriberKey', Value: 'myKey' }]}; //example contentJSON Array: [{ CustomerKey: custKey, Properties: [{ Name: 'SubscriberKey', Value: 'myKey' }]}, { CustomerKey: custKey, Properties: [{ Name: 'SubscriberKey', Value: 'myKey2' }]}] if(mid) { prox.resetClientIds(); //reset previous settings prox.setClientId({ "ID": mid }); //Impersonates the BU } var batch = isArray(contentJSON); if(batch) { var res = prox.UpdateBatch(soapObjName,contentJSON); } else { var res = prox.UpdateItem(soapObjName,contentJSON); } function isArray(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; return res; }
This also includes the isArray function to ensure it correctly assigns Batch or Item to your WSProxy call. See ‘Delete Method‘ for more info on this process.
As a note, updateBatch can actually be used to update into multiple data extensions in the same call. For example the array below passed in as the contentJSON
would update the first record in the DE with custKey1 and then would update the second record in the DE with custKey2.
[ { CustomerKey: custKey1, //CustomerKey of the DE Properties: [ { Name: 'SubscriberKey', Value: 'myKey' } ] }, { CustomerKey: custKey2, //CustomerKey of the DE Properties: [ { Name: 'SubscriberKey', Value: 'myKey' } ] } ]
Perform Method
This method allows you to perform an action on a single item or several items in a single call. Perform method can be called in two ways: performItem
and performBatch
.
performItem – This is used to perform a single action inside the specified SOAP Object. It will only require a JSON object specifying the action you want performed.
performBatch – This is used to perform multiple actions inside the specified SOAP Object. It will require a JSON array of objects specifying each action you want performed.
Perform has three required parameters and a single optional one
- ObjectType to perform action on
- Properties to set on APIObject where action is being performed.
- Verb used when executing the action
- Properties of PerformOptions you want included
//Perform specific object from identified SOAP Object function performGeneric(soapObjName,objProps,action,opts,mid) { //example soapObjName: "EmailContentCheck" //example objProps: { "Email": { "ID": 1234} } //example action: "Start" //example options: {} if(mid) { prox.resetClientIds(); //reset previous settings prox.setClientId({ "ID": mid }); //Impersonates the BU } var batch = isArray(objProps); if(batch) { var res = prox.performBatch(soapObjName,objProps,action,opts); } else { var res = prox.performItem(soapObjName,objProps,action,opts); } function isArray(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; return res; }
This also includes the isArray function to ensure it correctly assigns Batch or Item to your WSProxy call. See ‘Delete Method‘ for more info on this process.
Execute Method
The Execute method sounds like it should be the same as Perform as the words mean almost the same thing, but they actually are really different actions.
For example, Perform is used to take an existing object and run it against selected item(s), where Execute is pushing content into and executing a built-in function. The best analogy I can make for this is Perform is like an automation that runs other defined actions in an order depending on input, where Execute is like a computer program that performs pre-defined actions based on your input.
The only common use I have found for Execute is the LogUnsubEvent.
Execute has two required parameters:
- Array of Name/Value parameters to include in the call
- Name of the Execute request
//Execute specific object from identified SOAP Object function executeGeneric(props,name,mid) { //example props: [{ Name: "SubscriberKey", Value: "[email protected]" }, { Name: "EmailAddress", Value: "[email protected]" }, { Name: "JobID", Value: 18099 }, { Name: "ListID", Value: 8675309 }, { Name: "BatchID", Value: 0 }]; //example name: "LogUnsubEvent" //example for null prop: { "__Type__": "NullAPIProperty", Name: "SomeName", Value: ""} if(mid) { prox.resetClientIds(); //reset previous settings prox.setClientId({ "ID": mid }); //Impersonates the BU } var res = prox.execute(props, name); return res; }
Configure Method
No matter how far I dug nor who I asked, I simply could not find a working example or usage for the Configure method. There is a sample SOAP call shown in the docs, but no matter what I tried, it wouldn’t work for me. Also, if you look at the supported operations official document, none of the objects listed can be used with Config.
Based on the WSProxy docs, Configure has three required parameters and one optional:
- The Object Type to configure (for example ‘EmailContentCheck’)
- The properties to set on the APIObject being acted on.
- Verb to use when executing the action
- Any properties that you want to use from ConfigureOptions object.
Note: I have not had a successful run of this method in WSProxy, so the below is based on assumptions and what is written in the documentation.
//Configures specific object from identified SOAP Object function configureGeneric(soapObjName,objProps,action,opts,mid) { //example soapObjName: "PropertyDefinition" //example objProps: { "Name": "New_Attribute_Name", "PropertyType": "string" } //example action: "Create" | "Update" | "Delete" //example options: {} if(mid) { prox.resetClientIds(); //reset previous settings prox.setClientId({ "ID": mid }); //Impersonates the BU } var batch = isArray(objProps); if(batch) { var res = prox.configureBatch(soapObjName,objProps,action,opts); } else { var res = prox.configureItem(soapObjName,objProps,action,opts); } function isArray(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; return res; }
This also includes the isArray function to ensure it correctly assigns Batch or Item to your WSProxy call. See ‘Delete Method‘ for more info on this process.
Some Neat Things about WSProxy (And Some Gotchas)
I wanted to share a couple more things about WSProxy, including some ‘gotchas’ to watch out for.
For instance, in my Execute function, you may have noticed a comment talking about null properties. //example for null prop: { "__Type__": "NullAPIProperty", Name: "SomeName", Value: ""}
. This is something very important to note as it is not really documented anywhere as to what "__Type__"
means or is used for so this would be our first Gotcha.
It really seems to be replacing the attribute of ‘Type’ on the XML elements in the SOAP calls.
For example:
<Property type="example"><Name>myName</Name></Property> //is equal to: { Name: "myName", "__Type__": "example"}
To help show some uses I am aware of (and I will be happy to add more if anyone else has ones I missed), I am listing them out below:
So here are the current ‘use cases’ I am aware of:
- This is used when building an automation via WSProxy to show the Activity Object type. e.g.
"__Type__": "QueryDefinition"
. (Can see example inside of the Create WSProxy page) - If you have any null values you want to pass in a Create, Update, etc. as well, you will need to include a property of
"__Type__":"NullAPIProperty"
. This is a good example from a SFSE post by Sam Whitmore - When creating simple or complex filter parts, you need to declare what they are inside of the “Type” parameter. e.g.
"__Type__":"SimpleFilterPart"
Good example on SFSE post by Adam Spriggs
A second Gotcha to watch out for is that returned information from a Retrieve WSProxy call is not ready to be pushed back in ‘as is’ for a Create or Update WSProxy call.
If you take information from a retrieve and then use the exact returned object to try and create a copy (with changes made to key/name so its ‘new’) you can run into issues with trying to write to properties that are retrievable, but are not editable or writable. This will cause your Create call to error, despite it being all valid information. A good example of how to handle this is inside of my WSProxy to copy a data extension article.
A general point of advice is that if you are running into an issue with a property that makes no sense to you, you should first run a describe on the object and make sure that it is actual capable of doing what you want it to do and that it is spelled right, etc.
I will leave off with two use-case functions I find myself using regularly to optimize my WSProxy usage.
Get Retrievable Columns Function:
//returns all retrievable columns from SOAP Object function getRetrievableColumns(soapObjName) { var desc = prox.describe(soapObjName); var prop = desc.Results[0].Properties var fieldArr = []; for(i=0;i<prop.length; i++) { var item = prop[i]; item.IsRetrievable ? fieldArr.push(item.Name) : ''; } return fieldArr }
This will return an array of all retrievable functions from the specified SOAP Object.
Next is a WSProxy function to retrieve all the AutomationIDs of the Automations in your account.
function retrieveAllProgramIDs() { var prox = new Script.Util.WSProxy(); var cols = ["ObjectID"]; var filter = { Property: "Status", SimpleOperator: "IN", Value: [-1,0,1,2,3,4,5,6,7,8] }; var res = prox.retrieve("Program", cols, filter); return res; }
You may notice that I am pulling off of the Object “Program” instead of “Automation“. This is because Automation is very buggy and not always reliable. So I used the legacy (and no longer documented) Program Object.
For most of the Automation SOAP Objects, there still exists a Program version of it as well. I would not really recommend building forward facing processes based on these – but if you are in a pinch and the new Automation Objects will not work, then you can use these to supplement.
In Conclusion
I hope you enjoyed my WSProxy article and I hope to either add more to it or write a second one at a later date. There is still so much that needs to be said about this wonderful tool. As for the SSJS series, I may look to do one more article on ‘Native’ JS function usage in SFMC SSJS or I may move on to a different topic for now. As always free free to reach out with any questions or comments!
Hi Gortonington, thanks for the great article.
Just one rookie question, please correct me if I’m wrong. This SOAP API call does not count towards the API limits as they are being made inside Maketing Cloud, right?
Again, thank you very much!
FYI. When I try to retrieve an Automation with “CategoryID” field, the Object “Program” doesn’t work but threw out an error message “The Request Property(s) CategoryID do not match with the fields of Program retrieve”. Before that, I checked the available properties related to the Object “Program” but it did show “CategoryID” field.
The 2nd attempt with the same field in the Object “Automation” was good though.
Hi there! I was wondering, is there an “insert-batch” method? I was trying updateBatch function, but there are some caveats, for instance, if my data extension is empty I can not insert records. It seems to work only to update records already present in the DE, not for new ones.