Transforming Marketo-incompatible webhook responses using AWS API Gateway + JSONPath + Velocity (!)
Velocity knowledge is useful even outside of Marketo. I’ve been waiting for a ripped-from-the-headlines example, and along came this Marketo Nation question:
I’m calling a remote service via webhook. But the results aren’t coming back in a format the Response Mappings can understand (at least not from what I’ve tried). The response JSON looks like this:
{ "car_count" : 123456, "item_count" : 117733, "item_types" : [ { "item_type" : "Blue Car", "item_type_count" : 1115, "item_type_ratio" : 12 }, { "item_type" : "Red Car", "item_type_count" : 1225, "item_type_ratio" : 14 }, { "item_type" : "Yellow Car", "item_type_count" : 1345, "item_type_ratio" : 6 } ], "item_types_ratio" : 22 }
But I need to get the
item_type_count
for eachitem_type
to write to different fields. This would be easy if the JSON were like:
{ "Blue Car" : 1115, "Red Car" : 1225, "Yellow Car" : 1345 }
In case it’s not clear, the reason the original JSON response won’t work with webhook Response Mappings is that a Response Attribute entry always uses a string property name (which could be a numeric string array index[1]):
That is, you can reference “the first member of the item_types
array” (item_types[0]
) and get its item_type_count
: that’s item_types[0].item_type_count
.
But you can’t reference “the (first) member of the item_types
array that has the property item_type
set to Blue Car
” and get its item_type_count
. That would amount to seeking the value of a specific property in an object (not the name) and back-referencing the object. Not possible!
If you only had JSONPath
A value-based seek is possible in JSONPath, where you use script expressions, which are like callbacks. In JSONPath, you’d do $.item_types[?(@.item_type == "Blue Car")].item_type_count
.
Only Response Mappings don’t support JSONPath.
This is where you need an API gateway sitting between you and the original remote service:
- Marketo’s Call Webhook connects to the gateway
- the gateway seamlessly connects to the original service and gets the original response
- the gateway transforms/translates/trims the response, using JSONPath or some other language, on-the-fly
- the gateway returns transformed content to Marketo
- now, Marketo’s Response Mappings can parse the content as needed
Transformers are key parts of API gateways
API gateways typically emphasize their enterprise features: security, rate limits, load balancing, multiplexing, et al. But buried in nearly every feature list is something basic: a content transformer. (You can see it under “Manipulation - Transform” on KrakenD’s infographic.)
Transformers are usually marketed in terms of transforming XML to JSON or other such big promises. But sometimes you just need to transform JSON into slightly different JSON. The transformer can do that, too.
There are several players offering API-gateway-as-a-service, but I’m most familiar with Amazon’s. AWS API Gateway is cheap — free tier is a million requests per month — and it’s, well, easy enough to set up. (Not really easy, I concede.)
We’re going to build an AWS API GW definition for the remote service at https://originalendpoint.example.com
. The assumption for today is you typically GET that URL using a Marketo webhook.
First, Build a new REST API:
Confirm this is REST, not WebSocket:
Next, Create Method at the top level:
Choose GET from the dropdown:
Set the remote Integration Type to HTTP, and type the original Endpoint URL.
Now, the good stuff. Click Integration Response:
Add this simple Velocity script as the Mapping Template for the Content-Type application/json:
#set( $item_types = $input.path("$.item_types") )
{
#foreach( $type in $item_types )
"${util.escapeJavaScript($type.item_type).replaceAll("\\'","'")}" : ${type.item_type_count}#if( $foreach.hasNext ),#end
#end
}
Finally, Deploy API generates your unique Invoke URL. That’s what you swap in in the Marketo webhook definition.
Reviewing the Velocity code
The Velocity transformer code is very simple, as long as you have a little bit of Velocity experience to start with!
- Use JSONPath via AWS API Gateway’s built-in
$input.path()
method to get a list representation of theitem_types
objects. (ArrayList[2] of LinkedHashMaps, if you wanna be technical.) - Print the opening
{
of a top-level JSON object. - Iterate over the list. For each
$type
object in the list, output the quoted, JSON-encoded value of itsitem_type
property, then a colon:
, then the unquoted (since it’s a number) value of itsitem_type_count
property. - Print a comma
,
if necessary. - Print the closing
}
of the JSON object
You’ve thus transformed this original response:
{
"car_count" : 123456,
"item_count" : 117733,
"item_types" : [
{
"item_type" : "Blue Car",
"item_type_count" : 1115,
"item_type_ratio" : 12
},
{
"item_type" : "Red Car",
"item_type_count" : 1225,
"item_type_ratio" : 14
},
{
"item_type" : "Yellow Car",
"item_type_count" : 1345,
"item_type_ratio" : 6
}
],
"item_types_ratio" : 22
}
Into this:
{
"Blue Car" : 1115,
"Red Car" : 1225,
"Yellow Car" : 1345
}
The new JSON “shape” lets you use Response Mappings to capture the count for each color:
Caveat: Your AWS API Gateway setup isn’t really done
This post is meant to take you, say, 85% of the way toward a production API gateway hosted on AWS. There are some parts I couldn’t get to because the main thrust is about transformation using JSONPath + Velocity.
You should also require an API Key for authentication and add the corresponding X-Api-Key
header in Marketo » Webhooks Actions » Set Custom Header. And you’ll most likely need to declare URL Query String Parameters so they’re passed to the remote service.
Both of the above options are under Method Execution in the AWS Console.
Notes
[1] For you see, array indexes are actually string property names, even though we express them as integers.
[2] Even more technically, it’s a net.minidev.json.JSONArray of java.util.LinkedHashMap objects. JSONArray is a subclass of java.util.ArrayList.