How do I invoke an AWS Step Function using an API Gateway POST request, and the request's JSON payload to the Step Function ?
Quite obvious. I guess that if you're reading this you know how to do it.
Otherwise, you can go have a look at the documentation here: What is AWS Step Functions?.
It can be for either all Step Functions, or only this one. We'll only cover the first case, as explained in an Amazon tutorial: Creating an API Using API Gateway.
To create the IAM role
Log in to the AWS Identity and Access Management console.
On the Roles page, choose Create New Role.
On the Set Role Name page, type APIGatewayToStepFunctions for Role Name, and then choose Next Step.
On the Select Role Type page, under Select Role Type, select Amazon API Gateway.
On the Attach Policy page, choose Next Step.
On the Review page, note the Role ARN, for example:
arn:aws:iam::123456789012:role/APIGatewayToStepFunctions- Choose Create Role.
 To attach a policy to the IAM role
- On the Roles page, search for your role by name (APIGatewayToStepFunctions) and then choose the role.
 - On the Permissions tab, choose Attach Policy.
 - On the Attach Policy page, search for AWSStepFunctionsFullAccess, choose the policy, and then choose Attach Policy.
 
As explained by Ka Hou Ieong in How can i call AWS Step Functions by API Gateway?, you can create an AWS Service integration via API Gateway Console, like this:
Headers:
X-Amz-Target -> 'AWSStepFunctions.StartExecution'
Content-Type -> 'application/x-amz-json-1.0'
Body Mapping Templates/Request payload:
{
    "input": "string" (optional),
    "name": "string" (optional),
    "stateMachineArn": "string"
}
Everything is the same as in 2.a, except for the body mapping template. You have to do is make it into a string. Using $util.escapeJavascript(), like this for example. It will pass your whole request's body as an input to your Step Function
    #set($data = $util.escapeJavaScript($input.json('$')))
    {
        "input": "$data",
        "name": "string" (optional),
        "stateMachineArn": "string" (required)
    }
stateMachineArn: If you do not want to have to pass the stateMachineArn as part of your requests to API Gateway, you can simply hard-code it inside your Body Mapping Template (see AWS API Gateway with Step Function)name: Omitting the name property will have API Gateway generate a different one for you at each execution.Now, this is my first "Answer your own question", so maybe this is not how it's done, but I did spend quite a few hours trying to understand what was wrong with my Mapping Template. Hope this will help save other people's hair and time.
For those ones that are looking a way to directly connect ApiGateway with a Step Functions State Machine using the OpenApi integration and CloudFormation, this is an example of how I managed to make it work:
This is the Visual Workflow I designed (more details in the CloudFormation file) as a proof of concept:

template.yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Description: POC Lambda Examples - Step Functions
Parameters:
  CorsOrigin:
    Description: Header Access-Control-Allow-Origin
    Default: "'http://localhost:3000'"
    Type: String
  CorsMethods:
    Description: Header Access-Control-Allow-Headers
    Default: "'*'"
    Type: String
  CorsHeaders:
    Description: Header Access-Control-Allow-Headers
    Default: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
    Type: String
  SwaggerS3File:
    Description: 'S3 "swagger.yaml" file location'
    Default: "./swagger.yaml"
    Type: String
Resources:
  LambdaRoleForRuleExecution:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::StackName}-lambda-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action: 'sts:AssumeRole'
            Principal:
              Service: lambda.amazonaws.com
      Policies:
        - PolicyName: WriteCloudWatchLogs
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: 'arn:aws:logs:*:*:*'
  ApiGatewayStepFunctionsRole:
    Type: AWS::IAM::Role
    Properties:
      Path: !Join ["", ["/", !Ref "AWS::StackName", "/"]]
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: AllowApiGatewayServiceToAssumeRole
            Effect: Allow
            Action:
              - 'sts:AssumeRole'
            Principal:
              Service:
                - apigateway.amazonaws.com
      Policies:
        - PolicyName: CallStepFunctions
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'states:StartExecution'
                Resource:
                  - !Ref Workflow
  Start:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${AWS::StackName}-start
      Code: ../dist/src/step-functions
      Handler: step-functions.start
      Role: !GetAtt LambdaRoleForRuleExecution.Arn
      Runtime: nodejs8.10
      Timeout: 1
  Wait3000:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${AWS::StackName}-wait3000
      Code: ../dist/src/step-functions
      Handler: step-functions.wait3000
      Role: !GetAtt LambdaRoleForRuleExecution.Arn
      Runtime: nodejs8.10
      Timeout: 4
  Wait500:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${AWS::StackName}-wait500
      Code: ../dist/src/step-functions
      Handler: step-functions.wait500
      Role: !GetAtt LambdaRoleForRuleExecution.Arn
      Runtime: nodejs8.10
      Timeout: 2
  End:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${AWS::StackName}-end
      Code: ../dist/src/step-functions
      Handler: step-functions.end
      Role: !GetAtt LambdaRoleForRuleExecution.Arn
      Runtime: nodejs8.10
      Timeout: 1
  StateExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - !Sub states.${AWS::Region}.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: "StatesExecutionPolicy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "lambda:InvokeFunction"
                Resource:
                  - !GetAtt Start.Arn
                  - !GetAtt Wait3000.Arn
                  - !GetAtt Wait500.Arn
                  - !GetAtt End.Arn
  Workflow:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      StateMachineName: !Sub ${AWS::StackName}-state-machine
      RoleArn: !GetAtt StateExecutionRole.Arn
      DefinitionString: !Sub |
        {
          "Comment": "AWS Step Functions Example",
          "StartAt": "Start",
          "Version": "1.0",
          "States": {
            "Start": {
              "Type": "Task",
              "Resource": "${Start.Arn}",
              "Next": "Parallel State"
            },
            "Parallel State": {
              "Type": "Parallel",
              "Next": "End",
              "Branches": [
                {
                  "StartAt": "Wait3000",
                  "States": {
                    "Wait3000": {
                      "Type": "Task",
                      "Resource": "${Wait3000.Arn}",
                      "End": true
                    }
                  }
                },
                {
                  "StartAt": "Wait500",
                  "States": {
                    "Wait500": {
                      "Type": "Task",
                      "Resource": "${Wait500.Arn}",
                      "End": true
                    }
                  }
                }
              ]
            },
            "End": {
              "Type": "Task",
              "Resource": "${End.Arn}",
              "End": true
            }
          }
        }
  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Environment
      Name: !Sub ${AWS::StackName}-api
      DefinitionBody:
        'Fn::Transform':
          Name: AWS::Include
          Parameters:
            # s3 location of the swagger file
            Location: !Ref SwaggerS3File
swagger.yaml
openapi: 3.0.0
info:
  version: '1.0'
  title: "pit-jv-lambda-examples"
  description: POC API
  license:
    name: MIT
x-amazon-apigateway-request-validators:
  Validate body:
    validateRequestParameters: false
    validateRequestBody: true
  params:
    validateRequestParameters: true
    validateRequestBody: false
  Validate body, query string parameters, and headers:
    validateRequestParameters: true
    validateRequestBody: true
paths:
  /execute:
    options:
      x-amazon-apigateway-integration:
        type: mock
        requestTemplates:
          application/json: |
            {
              "statusCode" : 200
            }
        responses:
          "default":
            statusCode: "200"
            responseParameters:
              method.response.header.Access-Control-Allow-Headers:
                Fn::Sub: ${CorsHeaders}
              method.response.header.Access-Control-Allow-Methods:
                Fn::Sub: ${CorsMethods}
              method.response.header.Access-Control-Allow-Origin:
                Fn::Sub: ${CorsOrigin}
            responseTemplates:
              application/json: |
                {}
      responses:
        200:
          $ref: '#/components/responses/200Cors'
    post:
      x-amazon-apigateway-integration:
        credentials:
          Fn::GetAtt: [ ApiGatewayStepFunctionsRole, Arn ]
        uri:
          Fn::Sub: arn:aws:apigateway:${AWS::Region}:states:action/StartExecution
        httpMethod: POST
        type: aws
        responses:
          default:
            statusCode: 200
            responseParameters:
              method.response.header.Access-Control-Allow-Headers:
                Fn::Sub: ${CorsHeaders}
              method.response.header.Access-Control-Allow-Origin:
                Fn::Sub: ${CorsOrigin}
          ".*CREATION_FAILED.*":
            statusCode: 403
            responseParameters:
              method.response.header.Access-Control-Allow-Headers:
                Fn::Sub: ${CorsHeaders}
              method.response.header.Access-Control-Allow-Origin:
                Fn::Sub: ${CorsOrigin}
            responseTemplates:
              application/json: $input.path('$.errorMessage')
        requestTemplates:
          application/json:
            Fn::Sub: |-
              {
                "input": "$util.escapeJavaScript($input.json('$'))",
                "name": "$context.requestId",
                "stateMachineArn": "${Workflow}"
              }
      summary: Start workflow
      responses:
        200:
          $ref: '#/components/responses/200Empty'
        403:
          $ref: '#/components/responses/Error'
components:
  schemas:
    Error:
      title: Error
      type: object
      properties:
        code:
          type: string
        message:
          type: string
  responses:
    200Empty:
      description: Default OK response
    200Cors:
      description: Default response for CORS method
      headers:
        Access-Control-Allow-Headers:
          schema:
            type: "string"
        Access-Control-Allow-Methods:
          schema:
            type: "string"
        Access-Control-Allow-Origin:
          schema:
            type: "string"
    Error:
      description: Error Response
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
      headers:
        Access-Control-Allow-Headers:
          schema:
            type: "string"
        Access-Control-Allow-Origin:
          schema:
            type: "string" 
step-functions.js
exports.start = (event, context, callback) => {
    console.log('start event', event);
    console.log('start context', context);
    callback(undefined, { function: 'start' });
};
exports.wait3000 = (event, context, callback) => {
    console.log('wait3000 event', event);
    console.log('wait3000 context', context);
    setTimeout(() => {
        callback(undefined, { function: 'wait3000' });
    }, 3000);
};
exports.wait500 = (event, context, callback) => {
    console.log('wait500 event', event);
    console.log('wait500 context', context);
    setTimeout(() => {
        callback(undefined, { function: 'wait500' });
    }, 500);
};
exports.end = (event, context, callback) => {
    console.log('end event', event);
    console.log('end context', context);
    callback(undefined, { function: 'end' });
};
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With