5

I'm working with twilio in which when call comes to my twilio number it invokes webhook, I'm using lambda function as webhook,

twilio expects xml(formerly called twiml) response from webhook and i'm unable to send xml response from lambda function

I'm using serverless framework

here is my code function:

module.exports.voice = (event, context, callback) => {

  console.log("event", JSON.stringify(event))
  var twiml = new VoiceResponse();

  twiml.say({ voice: 'alice' }, 'Hello, What type of podcast would you like to listen? ');
  twiml.say({ voice: 'alice' }, 'Please record your response after the beep. Press any key to finish.');

  twiml.record({
    transcribe: true,
    transcribeCallback: '/voice/transcribe',
    maxLength: 10
  });

  console.log("xml: ", twiml.toString())

  context.succeed({
    body: twiml.toString()
  });
};

yml:

service: aws-nodejs

provider:
  name: aws
  runtime: nodejs6.10
  timeout: 10

iamRoleStatements:
    - Effect: "Allow"
      Action: "*"
      Resource: "*"

functions:
  voice:
    handler: handler.voice
    events:
      - http:
          path: voice
          method: post
          integration: lambda
          response:
            headers:
              Content-Type: "'application/xml'"
          template: $input.path("$")
          statusCodes:
                200:
                    pattern: '.*' # JSON response
                    template:
                      application/xml: $input.path("$.body") # XML return object
                    headers:
                      Content-Type: "'application/xml'"

Response: enter image description here enter image description here

please let me know if I'm making some mistake in code also created an issue on github

Thanks, Inzamam Malik

Inzamam Malik
  • 3,238
  • 3
  • 29
  • 61

7 Answers7

6

You don't need to mess with serverless.yml so much. Here is the simple way:

In serverless.yml...

functions:
  voice:
    handler: handler.voice
    events:
      - http:
          path: voice
          method: post

(response, headers, Content-Type, template, and statusCodes are not necessary)

Then you can just set the statusCode and Content-Type in your function.

So delete this part...

context.succeed({
    body: twiml.toString()
  });

... and replace it with:

const response = {
    statusCode: 200,
    headers: {
      'Content-Type': 'text/xml',
    },
    body: twiml.toString(),
};

callback(null, response);

Lambda proxy integration (which is the default) assembles it into a proper response.

Personally I find this way simpler and more readable.

humun
  • 61
  • 1
  • 3
  • (part 1) - as of Oct 2017 - the recommended approach for APIG+Lambda integration is lambda-proxy, which I agree as it does away with the crazy mapping templates. THIS ANSWER IS THE CORRECT ONE. IT WORKS. Just beware that the form data from Twilio will be in the lambda's "event" parameter in the "body" field, and will be ONE SINGLE, GIANT STRING. – Thiago Silva Oct 27 '17 at 16:52
  • (part 2) You will then need to parse that into a Key/Value pair to get to it more easily - basically, just move your parsing code from the mapping template to your actual function, and that will simplify your serverless yaml config, and will ensure other devs can understand what's going on by looking at function code directly instead of configuration. – Thiago Silva Oct 27 '17 at 16:52
1

you need your lambda to be a "proxy" type, so you set the body property. but just try to do

context.succeed(twiml.toString());

that will send the "string" as result directly

or use the callback param:

function(event, context, callback) {
   callback(null, twiml.toString())
}
UXDart
  • 2,500
  • 14
  • 12
  • your solution not working for me, can you please refer me to an example app or example code, so i will be able to download it and deploy on my aws account and see it in action – Inzamam Malik May 08 '17 at 07:48
  • if you change the line `context.succeed({ body: twiml.toString() });` with `callback(null, twiml.toString())` what error do you see? – UXDart May 08 '17 at 11:28
  • I get same response as before which you can see in attached images in question – Inzamam Malik May 08 '17 at 11:42
  • change your template from `$input.path("$.body")` to `$input.path('$')` – UXDart May 08 '17 at 15:19
0

As mentioned by @UXDart, you'll not be able to do this using the standard integration. You should setup a proxy integration with Lambda like here -http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html#api-gateway-proxy-integration-lambda-function-nodejs

This will work better with what you are trying to do, return xml through api gateway.

Abhigna Nagaraja
  • 1,874
  • 15
  • 17
0

Change your serverless.yml to this:

service: aws-nodejs

provider:
  name: aws
  runtime: nodejs6.10
  timeout: 10

iamRoleStatements:
    - Effect: "Allow"
      Action: "*"
      Resource: "*"

functions:
  voice:
    handler: handler.voice
    events:
      - http:
          path: voice
          method: post
          integration: lambda
          response:
            headers:
              Content-Type: "'application/xml'"
            template: $input.path("$")
            statusCodes:
                200:
                    pattern: '' # Default response method
                    template:
                      # Your script returns json, so match it here
                      application/json: $input.path("$.body")
                    headers:
                      Content-Type: "'application/xml'"
Dzmitry Bachko
  • 461
  • 4
  • 6
0

Got mine to work with this.

events:
      - http:
          path: call/receive
          method: post
          integration: lambda
          response:
            headers: 
              Content-Type: "'application/xml'"
            template: $input.path("$")
            statusCodes:
              200:
                pattern: ''
                template: 
                  application/json: $input.path("$")
                headers:
                  Content-Type: "'application/xml'"

and

callback(null, twiml.toString());
gchao
  • 320
  • 1
  • 3
  • 8
0

It works for me.

webhook:
    handler: webhook.webhook
    events:
      - http:
          path: webhook
          method: get
          cors: true
          integration: lambda
          response:
            headers:
              Content-Type: "'application/xml'"
            template: $input.path("$")
            statusCodes:
                200:
                    pattern: '' # Default response method
                    template:
                      # Your script returns json, so match it here
                      application/json: $input.path("$.body")
                    headers:
                      Content-Type: "'application/xml'"
-2

Checkout the twilio serverless example here: https://github.com/serverless/examples/tree/master/aws-node-twilio-send-text-message

DavidWells
  • 132
  • 1
  • 4