[{"data":1,"prerenderedAt":103},["ShallowReactive",2],{"pattern-lambda-web-webhooks-nodejs-sam":3,"pattern-group-lambda-web-webhooks-nodejs-sam":76},{"id":4,"title":5,"architectureURL":6,"cleanup":7,"contributors":10,"deploy":12,"description":15,"extension":16,"framework":17,"gitHub":18,"highlight":22,"introBox":23,"language":30,"level":31,"meta":32,"patternArch":33,"patternGroup":53,"patternGroupLabel":6,"resources":54,"stem":71,"testing":72,"videoId":6,"__hash__":75},"patterns\u002Fpatterns\u002Flambda-web-webhooks-nodejs-sam.json","Webhook ingestion on AWS Lambda Web Functions with Amazon DynamoDB",null,{"text":8},[9],"Delete the stack: \u003Ccode>sam delete --stack-name lambda-web-webhooks-sam\u003C\u002Fcode>.",[11],"content\u002Fcontributors\u002Fjon-loinaz-anton.json",{"text":13},[14],"See the GitHub repo for deployment instructions.","Receive Stripe, GitHub and generic webhooks on a Lambda Web Function, verify HMAC-SHA256 signatures and store each event once in DynamoDB.","json","AWS SAM",{"template":19},{"patternPath":20,"templateFile":21},"lambda-web-webhooks\u002Fnodejs\u002Fsam","template.yaml","true",{"headline":24,"text":25},"How it works",[26,27,28,29],"This pattern deploys a webhook receiver as an AWS Lambda Web Function with an Amazon DynamoDB table. Providers such as Stripe or GitHub POST directly to the function's HTTPS endpoint, which replaces the usual API Gateway plus Lambda pair with a single Express server. Because Lambda Web Functions forward the HTTP request unchanged, the app reads the raw request body, which is what signature verification needs; there is no base64 decoding or event mapping.","\u003Ccode>POST \u002Fwebhooks\u002F{provider}\u003C\u002Fcode> runs four steps. First it verifies the signature with the provider's scheme: Stripe's \u003Ccode>stripe-signature\u003C\u002Fcode> header (timestamp plus HMAC-SHA256 over \u003Ccode>t.body\u003C\u002Fcode>), GitHub's \u003Ccode>x-hub-signature-256\u003C\u002Fcode> header (\u003Ccode>sha256=\u003C\u002Fcode> prefix), or a generic \u003Ccode>x-webhook-signature\u003C\u002Fcode> hex digest. All comparisons use \u003Ccode>crypto.timingSafeEqual\u003C\u002Fcode>, and a missing or wrong signature returns 401 before the body is parsed. Second it parses the JSON. Third it derives a stable event id per provider (GitHub's \u003Ccode>x-github-delivery\u003C\u002Fcode> header, otherwise the payload \u003Ccode>id\u003C\u002Fcode>). Fourth it writes the event with a conditional \u003Ccode>PutItem\u003C\u002Fcode> on \u003Ccode>attribute_not_exists(eventId)\u003C\u002Fcode>: the first delivery is stored and later redeliveries return 200 with \u003Ccode>duplicate: true\u003C\u002Fcode>, so provider retries are idempotent. A TTL attribute expires events after 30 days.","The secrets arrive as \u003Ccode>NoEcho\u003C\u002Fcode> template parameters mapped to environment variables; in production load them from AWS Secrets Manager or SSM Parameter Store instead. \u003Ccode>POST \u002Ftest\u002Ffire\u003C\u002Fcode> builds a correctly signed sample request for any provider so the flow can be exercised without an external account. It signs with the real secret, so it is gated by the \u003Ccode>EnableTestEndpoint\u003C\u002Fcode> parameter, which defaults to \u003Ccode>false\u003C\u002Fcode>. \u003Ccode>GET \u002Fevents\u003C\u002Fcode> and \u003Ccode>GET \u002Fevents\u002F{eventId}\u003C\u002Fcode> read back what was stored.","A single \u003Ccode>AWS::Serverless::WebFunction\u003C\u002Fcode> resource expands into \u003Ccode>AWS::Lambda::WebFunction\u003C\u002Fcode>, an immutable \u003Ccode>AWS::Lambda::WebFunctionRevision\u003C\u002Fcode> and an \u003Ccode>AWS::Lambda::WebFunctionEndpoint\u003C\u002Fcode>. The endpoint uses \u003Ccode>AuthType\u003C\u002Fcode> \u003Ccode>ApplicationManaged\u003C\u002Fcode> because providers cannot SigV4-sign their calls; the HMAC signature is the authentication. The explicit execution role carries CloudWatch Logs plus \u003Ccode>PutItem\u003C\u002Fcode>, \u003Ccode>GetItem\u003C\u002Fcode> and \u003Ccode>Scan\u003C\u002Fcode> scoped to the table ARN.","Node.js","200",{},{"icon1":34,"icon2":39,"icon3":42,"line1":46,"line2":50},{"x":35,"y":36,"service":37,"label":38},15,50,"icon-internet","Webhook provider",{"x":36,"y":36,"service":40,"label":41},"lambda-web-functions","Lambda Web Function",{"x":43,"y":36,"service":44,"label":45},85,"dynamodb","Amazon DynamoDB",{"from":47,"to":48,"label":49},"icon1","icon2","POST + HMAC signature",{"from":48,"to":51,"label":52},"icon3","conditional PutItem","lambda-web-webhooks",{"bullets":55},[56,59,62,65,68],{"text":57,"link":58},"AWS Lambda documentation","https:\u002F\u002Fdocs.aws.amazon.com\u002Flambda\u002Flatest\u002Fdg\u002Fwelcome.html",{"text":60,"link":61},"AWS Serverless Application Model (SAM) documentation","https:\u002F\u002Fdocs.aws.amazon.com\u002Fserverless-application-model\u002Flatest\u002Fdeveloperguide\u002Fwhat-is-sam.html",{"text":63,"link":64},"Amazon DynamoDB: Time to Live (TTL)","https:\u002F\u002Fdocs.aws.amazon.com\u002Famazondynamodb\u002Flatest\u002Fdeveloperguide\u002FTTL.html",{"text":66,"link":67},"Stripe: verify webhook signatures","https:\u002F\u002Fdocs.stripe.com\u002Fwebhooks#verify-manually",{"text":69,"link":70},"GitHub: validating webhook deliveries","https:\u002F\u002Fdocs.github.com\u002Fen\u002Fwebhooks\u002Fusing-webhooks\u002Fvalidating-webhook-deliveries","patterns\u002Flambda-web-webhooks-nodejs-sam",{"text":73},[74],"See the GitHub repo for detailed testing instructions.","HaQLs9-CjnUCH4qUMcw6FbsBC_pQP8_BUCgZkFdWDsY",[77],{"id":4,"title":5,"architectureURL":6,"cleanup":78,"contributors":80,"deploy":81,"description":15,"extension":16,"framework":17,"gitHub":83,"highlight":22,"introBox":85,"language":30,"level":31,"meta":87,"patternArch":88,"patternGroup":53,"patternGroupLabel":6,"resources":94,"stem":71,"testing":101,"videoId":6,"__hash__":75},{"text":79},[9],[11],{"text":82},[14],{"template":84},{"patternPath":20,"templateFile":21},{"headline":24,"text":86},[26,27,28,29],{},{"icon1":89,"icon2":90,"icon3":91,"line1":92,"line2":93},{"x":35,"y":36,"service":37,"label":38},{"x":36,"y":36,"service":40,"label":41},{"x":43,"y":36,"service":44,"label":45},{"from":47,"to":48,"label":49},{"from":48,"to":51,"label":52},{"bullets":95},[96,97,98,99,100],{"text":57,"link":58},{"text":60,"link":61},{"text":63,"link":64},{"text":66,"link":67},{"text":69,"link":70},{"text":102},[74],1791847087659]