Receiving voice input with Amazon Connect
Tags: aws, beginners, javascript, tutorial
Published: 2024-02-05 11:12:00

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](https://dev.to/aws-heroes/outbound-voice-calls-on-aws-40mb).

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

![Amazon Connect Contact Flow Inbound](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lrirjv2mdjaq3e2x5zub.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/neh4iq24y8k82846rtzl.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kj0v7kyuur73y5wohkqk.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dlmwrfkomg412kxlr1i5.png)

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:

```json
{
    "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"
    }
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0r0k8l1tj91dl1i59210.png)

And then add the following information:

![Add Amazon Connect Permissions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yzh3p8f6fe7ritcp0e9x.png)

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:

```javascript
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;
};

```

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`

```javascript
const params = {
  DestinationPhoneNumber: {numberToCall},
  ContactFlowId: {contactFlowID},
  InstanceId: {connectInstanceId},
  QueueId: {QueueId},
  Attributes: { // Attributes
    "OTP": {OTP},
  },
  TrafficType: "GENERAL",
};
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8torp5kt0ewvghc7v3k.png)

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:

{% embed https://youtu.be/CQ6TdMCfFmM %}