DEV Community

Cover image for Amazon EventBridge API Destinations with parametrized endpoint URL and Mailchimp integration
Pawel Zubkiewicz for AWS Community Builders

Posted on • Updated on

Amazon EventBridge API Destinations with parametrized endpoint URL and Mailchimp integration

I love ❤️ working with Amazon EventBridge service. This is not a big surprise as it is beloved by the community. However, for me, it's not just the pure technical excellence of it.

Whenever I think about EventBridge I hear in my mind famous words from the TRON movie. They were even reused at the very beginning of the sequel TRON: Legacy.

I tried to picture clusters of information as they moved through the computer.
What do they look like? Ships? Motorcycles?
Were the circuits like freeways?
I kept dreaming of a world I thought I'd never see.
And then, one day...
I got in.

Even though those words were said about computer hardware not message bus per se, in my opinion they apply perfectly to it.

Whenever I work with EventBridge I have this sci-fi excitement that origins from that movie.

I hope you will get that vibe too!

What you will learn from the article

This article is not just about Mailchimp integration. You should read it if you want to learn how to:

  • setup Amazon EventBridge API Destinations to send REST requests to any 3rd party endpoint on the Internet
  • use HTTP Path Parameters to trigger different endpoints based on message content - this is huge as it is almost not described in the documentation, and there are no examples explaining that functionality.
  • setup and deploy sample project using Serverless Framework to learn by doing in your AWS account
  • integrate it with Mailchimp to tag users based on messages on your Event Bus
  • and finally, what to do when something doesn't work (how to debug API Destinations integrations)?

Business case

This tutorial is based on my experiences while implementing API Destinations integration with Mailchimp.

I have a small e-commerce platform that sells online courses. Whenever a customer buys any of the products I want to mark that information in my Email Service Provider (ESP). My ESP of choice is Mailchimp and I want to give a specific tag associated with the product to my customer, so I will not send him promotional emails about products that he already has. Unfortunately, my e-commerce platform is unable to tag users in Mailchimp, so I had to implement that myself.

Of course, I utilized serverless and as always it was fun & a lot of learning 😃

High-level architecture

Take a look at high-level architecture of the solution.

High-level architecture

Getting Orders from e-commerce platform is beyond the scope of this article. We're going to focus on EventBridge and integration with Mailichimp by using API Destinations. The assumption is that you know how to send a new event to a Custom Event Bus.

What is API Destinations?

API Destinations is relatively new functionality of Amazon EventBridge which allows us to integrate with 3rd party REST endpoints on the Internet directly without any Lambda functions!

All you need to do is to configure the service instead of writing your own code. As you're going to see, there is plenty of CloudFormation. However, you will get much better result compared to your own implementation.

This update follows the essential AWS principle stated by Chriss Munns during re:Invent 2019 use functions to transform, not transport.
Chriss Munns during re:Invent 2019
And that explains the whole idea behind it. Let's leave the mundane task of calling 3rd party endpoint (data transport) to AWS. We trade code (custom implementation) for configuration of API destinations. In return, we get a reliable solution that:

  • always works
  • is someone's else problem / responsibility
  • is being developed by best engineers there are. 😃

Implementation

In real life there is no place for clicking and defining infrastructure manually. Professional projects require automation, repeatability, reliability, and reusability.

Therefore, in this article instead of AWS Console screenshots I will present code excerpts following Infrastructure as Code principle.

If you're impatient, you can check this sample project on GitHub, that contains all the sources.

Solution components

Event Bus

To configure API Destinations we need our custom Event Bus, which is a backbone of our solution. It is simple to define, using CloudFormation.

    EventBus:
      Type: AWS::Events::EventBus
      Properties:
        Name: ${self:service}-${self:provider.stage}
Enter fullscreen mode Exit fullscreen mode

Both variables ${self:service} and ${self:provider.stage} come from Serverless Framework (SF). Let me explain, for those of you who are not fluent with SF. The first one resolves to the name of the project, and the second one to the name of a stage to which the service is deployed. That way there is no name clashing when different stages of the same project are deployed to the same region (or at all, as some resources are global i.e. S3 buckets, IAM Roles). Personally, I use dev, test and prod stage names in my projects.

Connection

Next we need AWS::Events::Connection object that contains authentication credentials (such as username and password) to the 3rd party endpoint.

    MailchimpConnection:
      Type: AWS::Events::Connection
      Properties:
        Name: MailchimpConnection-${self:provider.stage}
        AuthorizationType: BASIC
        AuthParameters:
            BasicAuthParameters:
                Username: "randomUser"
                Password: ${ssm:mailchimp-api-key~true}
Enter fullscreen mode Exit fullscreen mode

To login to Mailchimp we will use BASIC authorization type. In case of the Username parameter Mailchimp allows any string and is only interested in the Password part. From Mailchimp perspective Password value is an Api Key that can be generated under Account->Extras-> API Keys.

Basic Authorization Type configuration will result in the authorization header added to the request created by API Destinations when calling Mailchimp REST endpoint. It looks something like that:

authorization   Basic cmFuZG9tVXNlcjpZlPkmY2hpbXBIcGlQYXNzd5CyZA==
Enter fullscreen mode Exit fullscreen mode

Value of the Password parameter is set to ${ssm:mailchimp-api-key~true} which is again Serverless Framework specific. It retrieves secret value from Systems Manager Parameter Store (ssm prefix) during deployment. Parameter is of name mailchimp-api-key and ~true means it must be decrypted. More on this in Serverless Framework documentation.

ApiDestination

What could be strange for you is that we didn't define endpoint URL in the connection object. This is done in AWS::Events::ApiDestination.

    MailchimpApiDestination:
      Type: AWS::Events::ApiDestination
      Properties:
        Name: mailchimp-tag-${self:provider.stage}
        ConnectionArn: !GetAtt MailchimpConnection.Arn
        InvocationEndpoint: ${self:custom.mailchimp.endpoint}/lists/${self:custom.mailchimp.list}/segments/*/members
        HttpMethod: POST
        InvocationRateLimitPerSecond: 20
Enter fullscreen mode Exit fullscreen mode

Here we have three interesting elements:

  • InvocationEndpoint
  • HttpMethod
  • InvocationRateLimitPerSecond

The InvocationEndpoint contains two custom variables that contain my specific Mailchimp URL and mailing list Id. This particular URL allows adding a user to the tag (named as segment in Mailchimp). However, what's fascinating from API Destinations perspective is the * character in URL. It works as a placeholder which will be replaced by the HTTP Path Parameter value, taken from the Order event. We will define that parameter later.

Right now I want you to fully grasp the meaning of that. The endpoint URL can be parametrized based on event payload sent to the EventBus. This is great, as allows us to reuse single ApiDestination configuration to send data to complex endpoints.

The InvocationRateLimitPerSecond is another great treat of API Destinations. It allows us to limit rate of requests fired by API Destinations to the 3rd party. Obviously, it's a crucial thing to consider during integrations, as not all systems are as scalable as AWS serverless solutions. Here we get that for free and without any additional coding.

I hope this incredibly usefull setting will be added to other contstructs in EventBridge family. I could imagine rate limit added to existing Lambda targets or Simple Email Service targets (which dear AWS would be awesome to have 🤞) 😉

Rule

The last EventBridge specific element of the puzzle is AWS::Events::Rule that includes so called Target. The Rule object acts as a router or dispatcher. It tells our custom EventBus which events send where. The Target defines the where part and ties together Rule with new API Destinations functionality.

    ApiDestinationDeliveryRule:
      Type: AWS::Events::Rule
      Properties:
        EventBusName: !Ref EventBus
        Name: SendOrderEventsToApiDestinations
        EventPattern:
          detail-type:
            - ORDER_COURSE_SERVERLESS
            - ORDER_COURSE_DATALAKE
        State: "ENABLED"
        Targets:
          - Id: MailchimpApiDestination
            Arn: !GetAtt MailchimpApiDestination.Arn
            RoleArn: !GetAtt ApiDestinationsTargetRole.Arn
            InputTransformer:
              InputPathsMap:
                mcTagId: $.detail.mcTagId
                email: $.detail.email
              InputTemplate: >
                {
                  "email_address": <email>
                }
            HttpParameters:
              PathParameterValues:
                - $.detail.mcTagId
Enter fullscreen mode Exit fullscreen mode

In the EventPattern section it is defined to which events this rule applies to. In my case, it is limited only to Order events.

Now let's talk about targets. A single Rule can define many of them, here we have just one, named MailchimpApiDestination. Target refers to defined above ApiDestination, it requires a dedicated IAM Role.

It also defines InputTransformer, that allows us to modify event payload and tailor it to the 3rd party API specification. In my case, it's Mailchimp's Add member to segment method, which expect single value under email_address variable in JSON.

Event values can be access by $.detail and used in the InputTemplate section to construct expected object structure.

The last section allows us to parametrize endpoint URL defined in ApiDestination object with values from the event. PathParameterValues takes an array of values, which means that we can define more than one placeholder in the URL. They are resolved in order, the first element in the array refers to the first * in the URL, and so on.

IAM Role

We also need to define a Role that can be assumed by a events.amazonaws.com service. This role must grant events:InvokeApiDestination on MailchimpApiDestination resource. The full definition of the role can be found in the sample project on GitHub in serverless.yml file.

Sample solution deployment

I have prepared a sample project using Serverless Framework, so you can deploy it on your AWS account and play with it.

You need to clone the code, and install dependencies:

git clone https://github.com/serverlesspolska/eventbridge-api-destinations-mailchimp.git
cd eventbridge-api-destinations-mailchimp
npm i
Enter fullscreen mode Exit fullscreen mode

This requires node.js and npm be installed on your machine.

As not everyone uses Mailchimp, in the project I defined a second target that sends REST requests to webhook.site service. This is a free, easy to use web application that will work as our 3rd party endpoint.

Please go to the webhook.site and copy Your unique URL. Paste that URL into serverless.yml config file in line 38.

Now we're ready to deploy the project to the dev stage using command:

sls deploy
Enter fullscreen mode Exit fullscreen mode

(This assumes that you have your default AWS profile defined under ~/.aws/credentials.)

After successful deployment (can take several minutes) you can invoke a Lambda function, that will send a sample Order event to the EventBus.

sls invoke -f sendOrderEvent -l
Enter fullscreen mode Exit fullscreen mode

As a result, you should see the new request on the webhook.site website. (If don't refresh the page)

That means that API Destinations just called the endpoint responding to the new message on the EventBus.

Request received by WebHook Site

How to debug EventBridge API Destinations?

If you misconfigured something, or perhaps your credentials are wrong you will not get any error messages, there are no logs that you can read and try to determine what happened. All you can do is to check 3rd party system and see if the request came to it - that's why we used webhook.site.

What can we do to find out what's the root cause if the request doesn't hit the endpoint at all?

Fortunately, API Destinations allows the use of Dead Letter Queues (DLQ). For each Target defined under AWS::Events::Rule we can specify SQS queue that will contain messages that couldn't be successfully processed by the Target API Destination. Each message that ends up in the DLQ is annotated with information explaining the problem.

Here are two samples of error messages that I managed to get.
Wrong Mailchimp API Key This one was returned when I set wrong Mailchimp API Key.

Lack of privileges This one was caused by the wrong IAM Role definition.

Summary

I hope you found that article informative, and now you know how to use Amazon EventBridge API Destinations in your projects. Should you have any questions ask them below or on GitHub project site.

I'm convinced that this is a great functionality, even if it took me several hours to understand and implement it correctly for the first time. In the long run, it will save me much more on implementing and debugging Lambda functions that call 3rd party endpoints. They seem to be trivial to implement, but in reality they are not (How would you implement InvocationRateLimitPerSecond functionality in Lambda in a way that you don't pay for idle time?) and I will not miss them. Remember use functions to transform, not transport.

What is really cool and worth stressing out is that this functionality is fully developed and comes with all the parts. Most importantly there is full CloudFormation support which I must sadly admit was not always the case. Kudos to the CloudFormation team! 🙏

Amazon EventBridge is a central point of many serverless solutions, and I'm glad it is still being developed. API Destinations is a great addition to that service and hopefully indicator of many more to come.

Latest comments (10)

Collapse
 
neophytte profile image
neophytte

No matter what I do, I can't seem to get the MailChimp API endpoint to work; initially I was getting a 404 with InvocationEndpoint set as ${self:custom.webhook.endpoint}/lists/${self:custom.mailchimp.list}/segments//members* - then I set the endpoint to ${self:custom.mailchimp.endpoint}/lists/${self:custom.mailchimp.list}/members (which directly writes to a list), and modified the JSON payload to:

              InputTemplate: >
                {
                  "email_address": <email>,
                  "status": "subscribed",
                  "interests": {
                    "0ce6ed21da": true
                  }
                }

which works when submitted via Thunderclient or Postman, but shows an error in the attributes SDK_CLIENT_ERROR with Unable to invoke ApiDestination endpoint: The request failed due to an internal validation error. - is there anyway to uncover what the actual error is for the validation?

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
neophytte profile image
neophytte

Found it ... sls = serverless:

npm install serverless --global

Collapse
 
gkgourav45 profile image
gkgourav45

Additional info
It is not possible to define what a successful call means for retry. EventBridge will assume this from the returned HTTP status code

Collapse
 
gkgourav45 profile image
gkgourav45

Is it possible to store api calls and their response?

Collapse
 
seththomas profile image
Seth Geoghegan

Great post! The most thorough coverage of the topic I've seen to date, thanks for taking the time to put it together.

Based on my reading of this topic, it sounds like API Destinations only provide one-way communication with 3rd party APIs. Every example I've seen so far is sending event notifications to external APIs (e.g. a POST request to a ticketing system, sending mail, etc). None of these examples show how to fetch data from a 3rd party API in response to an event. Do you know if this is possible?

Here's some context to why I'm asking. My application ingests webhook notifications from activity tracker APIs (e.g. Strava, Fitbit, etc). The events themselves are just notifications that an activity happened (time, event ID, user id). To get more information from the API, I need to issue a GET request to the 3rd party API and process the response. I'd love if I could use Event Bridge and API Destinations to automatically fetch data in response to webhook events being sent into my system. However, this doesn't sound possible with the current EventBridge offering.

Collapse
 
pzubkiewicz profile image
Pawel Zubkiewicz

Thank you Seth.

You're correct. This mechanism works in just a one way (from AWS to a 3rd party endpoint).

As it goes about your application you may research AWS API Gateway service. This example serverlessrepo.aws.amazon.com/appl... shows how to implement a webhook that sends info to SNS. You may replace SNS with EventBridge to have single messaging channel.

Collapse
 
rcoundon profile image
Ross Coundon

We're just investigating this as a means of rate limiting requests to a 3rd party API from lambda.
There is an option in the API Destination HTTP Method for GET requests, does this mean it is possible to retrieve data using this method?

Collapse
 
boyney123 profile image
David Boyne

Nice write-up Pawel, thanks for taking the time and the examples given.

I personally love this feature and cannot wait to get my hands on it with a use case and start using it. This is the future for sure!

Collapse
 
pzubkiewicz profile image
Pawel Zubkiewicz

Thans a lot, David. Yes, this functionality is cool.

When I will have some free time I will add integration with my private Slack channel to get notifications about new customers.

James Beswick shared code that does that: github.com/aws-samples/serverless-...