DEV Community

Cover image for Receiving voice input with Amazon Connect
Matt Lewis for AWS Heroes

Posted on

Receiving voice input with Amazon Connect

I wrote a blog post last week that looked at two different options for making an outbound voice call to a customer to provide them with a piece of information such as a One Time Password (OTP).

One question I was asked following this, was what would happen if you needed the customer to provide you with some information over the phone (whether a passcode, or credit card number, or some other type of identification).

After discounting Amazon Pinpoint last week, let's take a look at how you can capture and validate data input by a customer with Amazon Connect.

This solution builds on the demo application from the last blog post which can be found here.

The final part of the contact flow when complete looks as follows:

Amazon Connect Contact Flow Inbound

Store Customer Input

The first block is a Store Customer Input. I include an audio prompt that tells the customer to enter their 5 digit passcode followed by the hash key as a terminating key press. The digits entered via DTMF are saved in the Stored customer input system attribute. This block also provides additional capabilities, such as allowing you to encrypt the data.

Store Customer Input block

Invoke AWS Lambda Function

The Store Customer Input is connected to an Invoke AWS Lambda function block. This is used to invoke a Lambda function and return key/value pairs that are set as contact attributes. The Function ARN can only be set once we have created our Lambda function, but we can set the function input to include the value captured in the previous block in an attribute called enteredOTP.

AWS Lambda function input

Create AWS Lambda Function

Next up is to create the actual AWS Lambda function that the block will invoke. To keep this simple, we create a new function in the AWS Lambda console using the Hello world function blueprint as shown in the screen shot below:

Create AWS Lambda function

If we were to try and invoke this function now from our contact flow, it would fail with an AccessDeniedException. We would be able to see this in the CloudWatch Log Group for the Amazon Connect instance:

{
    "Results": "Status Code: 403; Error Code: AccessDeniedException; RequestId: 404024fb-caac-449f-bec9-7d1fbde36a14",
    "ContactId": "87a81f2f-b529-4b83-a0af-4a540dd9ac85",
    "ContactFlowId": "arn:aws:connect:eu-west-2:424727766526:instance/83dfabfe-aced-428a-9595-faab3db13c20/contact-flow/a24c9bb7-062e-45c0-a138-962c4efde045",
    "ContactFlowName": "Outbound Call OTP",
    "ContactFlowModuleType": "InvokeExternalResource",
    "Identifier": "Invoke OTP Function",
    "Timestamp": "2024-01-29T21:26:12.039Z",
    "Parameters": {
        "FunctionArn": "arn:aws:lambda:eu-west-2:424727766526:function:logOTPFunction",
        "ResponseValidation": "ResponseType=STRING_MAP",
        "TimeLimit": "3000"
    }
}
Enter fullscreen mode Exit fullscreen mode

The AccessDeniedException error message means the AWS Lambda function's resource-based policy doesn't grant Amazon Connect permission to invoke the function.

To fix this, we can go into the AWS Lambda console and click on add permissions:

Resource Based Policy Statement

And then add the following information:

Add Amazon Connect Permissions

The source ARN is the ARN of your Amazon Connect instance. This policy grants permission for your Amazon Connect instance to invoke this function.

We edit the code of the Lambda function in the console to the following:

export const handler = async (event, context) => {
    console.log('Received event:', JSON.stringify(event, null, 2));
    const originalOTP = event.Details.ContactData.Attributes.OTP;
    const inputOTP = event.Details.Parameters.enteredOTP;
    console.log(`Original OTP was ${originalOTP} and input OTP is ${inputOTP}`);

    let responseMessage;
    let isValid = false;

    if (originalOTP === inputOTP) {
        responseMessage = 'The number you entered is the same value as we sent you';
        isValid = true;

    } else {
        responseMessage = 'The number you entered is not equal to the value we sent you';
        isValid = false;
    }

    const resultMap = {
        'responseMessage': responseMessage,
        'isValid': isValid
    }

    return resultMap;
};

Enter fullscreen mode Exit fullscreen mode

The first thing this code does is to extract the original OTP that is passed into the contact flow when it is invoked. You can find out more in the first blog post. This shows that when the StartOutboundVoiceContactCommand API call is made, it is passed in an attribute with the key of OTP

const params = {
  DestinationPhoneNumber: {numberToCall},
  ContactFlowId: {contactFlowID},
  InstanceId: {connectInstanceId},
  QueueId: {QueueId},
  Attributes: { // Attributes
    "OTP": {OTP},
  },
  TrafficType: "GENERAL",
};
Enter fullscreen mode Exit fullscreen mode

The value the customer entered on their keypad is retrieved from the parameters section with the key value as specified in the function parameter input section as shown previously.

Now we are in the Lambda function, we can make external API calls, or perform system lookups, or any other logic required. We just quickly compare the two passcode values to see if they are the same, and then return a message and a boolean value in a result map. This is essentially just a JSON structure.

Set Contact Attributes

The Invoke AWS Lambda function block is connected to a Set Contact Attributes block.

Set Contact Attributes

This block is used to copy the attributes returned in the JSON structure from the Lambda function into user-defined attributes.

You start by clicking in the block to add another attribute. The attribute value will be mapped to a key in the user defined namespace against the current contact. You set the value dynamically using the key name from the JSON structure returned and the External namespace.

Play Prompt

Finally, we add a Play Prompt block to the flow and set the text to spoken to the customer to the value of the response message attribute saved in the previous block: $.Attributes.ResponseMessage

Watch the following video to see this in action:

Top comments (0)