├── .gitignore
├── frontend
├── .env.example
├── babel.config.js
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── assets
│ │ └── logo.png
│ ├── main.js
│ ├── App.vue
│ └── components
│ │ ├── CreateWebhook.vue
│ │ ├── WebhookCallHistory.vue
│ │ └── CreateCandidate.vue
├── .gitignore
├── README.md
└── package.json
├── backend
├── functions
│ ├── fetch-resource-data
│ │ ├── package.json
│ │ └── app.js
│ ├── register-webhook
│ │ ├── package.json
│ │ └── app.js
│ ├── fetch-webhook-history
│ │ ├── package.json
│ │ └── app.js
│ ├── trigger-resource-created
│ │ ├── package.json
│ │ └── app.js
│ ├── call-webhook
│ │ ├── package.json
│ │ └── app.js
│ ├── create-candidate
│ │ ├── package.json
│ │ └── app.js
│ └── create-webhook-call
│ │ ├── package.json
│ │ └── app.js
├── .gitignore
├── statemachine
│ └── webhook_management.asl.json
├── README.md
└── template.yaml
└── Readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
--------------------------------------------------------------------------------
/frontend/.env.example:
--------------------------------------------------------------------------------
1 | VUE_APP_API_BASE_URL=https://${api}.execute-api.${region}.amazonaws.com/${stage}/
--------------------------------------------------------------------------------
/frontend/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubudusj/webhook_management/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubudusj/webhook_management/HEAD/frontend/src/assets/logo.png
--------------------------------------------------------------------------------
/frontend/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 |
4 | import "bootstrap/dist/css/bootstrap.min.css"
5 | import "bootstrap"
6 |
7 | createApp(App).mount('#app')
8 |
--------------------------------------------------------------------------------
/backend/functions/fetch-resource-data/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webhook_management",
3 | "version": "1.0.0",
4 | "description": "Webhook management system",
5 | "main": "app.js",
6 | "author": "SAM CLI",
7 | "license": "MIT",
8 | "dependencies": {
9 | "valid-url": "^1.0.9"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/backend/functions/register-webhook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webhook_management",
3 | "version": "1.0.0",
4 | "description": "Webhook management system",
5 | "main": "app.js",
6 | "author": "SAM CLI",
7 | "license": "MIT",
8 | "dependencies": {
9 | "valid-url": "^1.0.9"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/backend/functions/fetch-webhook-history/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webhook_management",
3 | "version": "1.0.0",
4 | "description": "Webhook management system",
5 | "main": "app.js",
6 | "author": "SAM CLI",
7 | "license": "MIT",
8 | "dependencies": {
9 | "valid-url": "^1.0.9"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/backend/functions/trigger-resource-created/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webhook_management",
3 | "version": "1.0.0",
4 | "description": "Webhook management system",
5 | "main": "app.js",
6 | "author": "SAM CLI",
7 | "license": "MIT",
8 | "dependencies": {
9 | "valid-url": "^1.0.9"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/backend/functions/call-webhook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webhook_management",
3 | "version": "1.0.0",
4 | "description": "Webhook management system",
5 | "main": "app.js",
6 | "author": "SAM CLI",
7 | "license": "MIT",
8 | "dependencies": {
9 | "axios": "^0.24.0",
10 | "crypto-js": "^4.1.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/backend/functions/create-candidate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webhook_management",
3 | "version": "1.0.0",
4 | "description": "Webhook management system",
5 | "main": "app.js",
6 | "author": "SAM CLI",
7 | "license": "MIT",
8 | "dependencies": {
9 | "email-validator": "^2.0.4",
10 | "uuid": "^8.3.2"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/backend/functions/create-webhook-call/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webhook_management",
3 | "version": "1.0.0",
4 | "description": "Webhook management system",
5 | "main": "app.js",
6 | "author": "SAM CLI",
7 | "license": "MIT",
8 | "dependencies": {
9 | "crypto-js": "^4.1.1",
10 | "uuid": "^8.3.2"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env
8 | .env.local
9 | .env.*.local
10 |
11 | # Log files
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | pnpm-debug.log*
16 |
17 | # Editor directories and files
18 | .idea
19 | .vscode
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # frontend
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | yarn lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Manage webhooks at scale with AWS Serverless
2 |
3 | This repository contains the project source code for webhook management system built with AWS Serverless services.
4 |
5 | You can find the blog post with set up details at:
6 | #### [https://pubudu.dev/posts/manage-webhooks-at-scale-with-aws-serverless/](https://pubudu.dev/posts/manage-webhooks-at-scale-with-aws-serverless/)
7 |
8 | 
9 |
10 | 
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/backend/functions/fetch-resource-data/app.js:
--------------------------------------------------------------------------------
1 | const AWS = require("aws-sdk");
2 | var docClient = new AWS.DynamoDB.DocumentClient();
3 |
4 | exports.lambdaHandler = async (event, context) => {
5 | if (!event.key || !event.type) {
6 | throw new Error("Required fields not found. key and type required");
7 | }
8 |
9 | let data = await fetchResourceData(event.key, event.type);
10 |
11 | if (data !== undefined) {
12 | return data;
13 | } else {
14 | return null;
15 | }
16 | };
17 |
18 | async function fetchResourceData(pk, type) {
19 | var params = {
20 | Key: {
21 | pk: pk,
22 | type: type,
23 | },
24 | TableName: process.env.DB_TABLE,
25 | };
26 |
27 | let data = await docClient.get(params).promise();
28 |
29 | return data.Item;
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Webhook Management with Serverless
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
33 |
34 |
44 |
--------------------------------------------------------------------------------
/backend/functions/trigger-resource-created/app.js:
--------------------------------------------------------------------------------
1 | const AWS = require("aws-sdk");
2 | const eventbridge = new AWS.EventBridge();
3 |
4 | exports.lambdaHandler = async (event, context) => {
5 | var eventsToPublish = [];
6 | for (const record of event.Records) {
7 | if (
8 | record.eventName === "INSERT" &&
9 | record.dynamodb.NewImage.type.S === "candidate"
10 | ) {
11 | let pk = record.dynamodb.NewImage.pk.S;
12 | let companyId = record.dynamodb.NewImage.companyId.S;
13 |
14 | let payload = {
15 | companyId: companyId,
16 | webhookEvent: "candidate.created",
17 | resourceType: "candidate",
18 | resourceId: pk,
19 | };
20 |
21 | eventsToPublish.push({
22 | Source: process.env.EVENT_SOURCE,
23 | EventBusName: process.env.EVENT_BUS,
24 | DetailType: "candidate.created",
25 | Time: new Date(),
26 | Detail: JSON.stringify(payload),
27 | });
28 | }
29 | }
30 |
31 | if (eventsToPublish.length > 0) {
32 | await eventbridge.putEvents({ Entries: eventsToPublish }).promise();
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "@popperjs/core": "^2.10.1",
12 | "axios": "^0.21.4",
13 | "bootstrap": "^5.1.1",
14 | "core-js": "^3.18.0",
15 | "email-validator": "^2.0.4",
16 | "validator": "^13.7.0",
17 | "vue": "^3.0.0"
18 | },
19 | "devDependencies": {
20 | "@vue/cli-plugin-babel": "~4.5.0",
21 | "@vue/cli-plugin-eslint": "~4.5.0",
22 | "@vue/cli-service": "~4.5.0",
23 | "@vue/compiler-sfc": "^3.0.0",
24 | "babel-eslint": "^10.1.0",
25 | "eslint": "^6.7.2",
26 | "eslint-plugin-vue": "^7.0.0",
27 | "vue-loader-v16": "^16.0.0-beta.5.4"
28 | },
29 | "eslintConfig": {
30 | "root": true,
31 | "env": {
32 | "node": true
33 | },
34 | "extends": [
35 | "plugin:vue/vue3-essential",
36 | "eslint:recommended"
37 | ],
38 | "parserOptions": {
39 | "parser": "babel-eslint"
40 | },
41 | "rules": {}
42 | },
43 | "browserslist": [
44 | "> 1%",
45 | "last 2 versions",
46 | "not dead"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/backend/functions/call-webhook/app.js:
--------------------------------------------------------------------------------
1 | const AWS = require("aws-sdk");
2 | const hmacSHA256 = require("crypto-js/hmac-sha256");
3 | const axios = require("axios");
4 | var stepfunctions = new AWS.StepFunctions();
5 |
6 | exports.lambdaHandler = async (event, context) => {
7 | for (const record of event.Records) {
8 | console.log(record)
9 | let body = JSON.parse(record.body);
10 | let payload = body["payload"];
11 | let url = body["url"];
12 | let taskToken = body["taskToken"];
13 |
14 | try {
15 | const webhook = await axios.post(url, payload, {
16 | "Content-Type": "application/json",
17 | });
18 |
19 | let params = {
20 | taskToken: taskToken,
21 | output: JSON.stringify({
22 | status: "success",
23 | output: {},
24 | }),
25 | };
26 |
27 | await stepfunctions.sendTaskSuccess(params).promise();
28 |
29 | console.log("Stepfunction notified with task success");
30 | } catch (error) {
31 | let params = {
32 | taskToken: taskToken,
33 | cause: "Webhook call failure",
34 | error: error.message,
35 | };
36 |
37 | await stepfunctions.sendTaskFailure(params).promise();
38 |
39 | console.log("Stepfunction notified with task failed");
40 | }
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/backend/functions/create-webhook-call/app.js:
--------------------------------------------------------------------------------
1 | const AWS = require("aws-sdk");
2 | var docClient = new AWS.DynamoDB.DocumentClient();
3 | const hmacSHA256 = require("crypto-js/hmac-sha256");
4 | const { v4: uuidv4 } = require("uuid");
5 |
6 | exports.lambdaHandler = async (event, context) => {
7 | let webhookPayload = event.taskResult.resourceData;
8 | webhookPayload.id = webhookPayload.pk;
9 | let url = event.webhookUrl;
10 | let signingToken = event.webhookSignToken;
11 | let resourceId = webhookPayload.pk;
12 | let currentTime = new Date().toISOString();
13 |
14 | delete webhookPayload.pk;
15 | delete webhookPayload.companyId;
16 |
17 | let postData = {
18 | resource: webhookPayload,
19 | resourceId: resourceId,
20 | resourceType: webhookPayload.type,
21 | triggeredAt: currentTime,
22 | token: hmacSHA256(resourceId + currentTime, signingToken).toString(),
23 | };
24 |
25 | let pk = event.webhookId + '_' + webhookPayload.id + '_' + uuidv4();
26 | var params = {
27 | Item: {
28 | pk: pk,
29 | type: "webhookcall",
30 | url: event.webhookUrl,
31 | companyId: event.companyId,
32 | payload: postData,
33 | status: 'pending',
34 | createdAt: currentTime,
35 | },
36 | ReturnConsumedCapacity: "TOTAL",
37 | TableName: process.env.DB_TABLE,
38 | ConditionExpression: "pk <> :pk",
39 | ExpressionAttributeValues: {
40 | ":pk": pk,
41 | },
42 | };
43 |
44 | await docClient.put(params).promise();
45 |
46 | return {
47 | 'id': pk,
48 | 'payload': postData
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/backend/functions/fetch-webhook-history/app.js:
--------------------------------------------------------------------------------
1 | const AWS = require("aws-sdk");
2 | var docClient = new AWS.DynamoDB.DocumentClient();
3 |
4 | exports.lambdaHandler = async (event, context) => {
5 | let companyId = event.pathParameters.companyId;
6 |
7 | if (!companyId) {
8 | return {
9 | statusCode: 422,
10 | headers: {
11 | "Content-Type": "application/json",
12 | "Access-Control-Allow-Origin": "*",
13 | },
14 | body: JSON.stringify({
15 | message: "Validation errors",
16 | errors: "companyId required",
17 | }),
18 | };
19 | }
20 |
21 | let data = await fetchWebhookHistory(companyId);
22 |
23 | if (data !== undefined) {
24 | result = data;
25 | } else {
26 | result = [];
27 | }
28 |
29 | return {
30 | statusCode: 200,
31 | headers: {
32 | "Content-Type": "application/json",
33 | "Access-Control-Allow-Origin": "*",
34 | },
35 | body: JSON.stringify({
36 | data: result,
37 | }),
38 | };
39 | };
40 |
41 | async function fetchWebhookHistory(companyId) {
42 | var params = {
43 | IndexName: "gsiTypeAndCompanyId",
44 | KeyConditionExpression: "#type = :type and companyId = :companyId",
45 | ExpressionAttributeValues: {
46 | ":type": "webhookcall",
47 | ":companyId": companyId,
48 | },
49 | ExpressionAttributeNames: {
50 | "#type": "type",
51 | "#status": "status",
52 | "#url": "url",
53 | "#output": "output"
54 | },
55 | ProjectionExpression: "pk, companyId, createdAt, #status, payload, #url, #output",
56 | TableName: process.env.DB_TABLE,
57 | };
58 |
59 | let data = await docClient.query(params).promise();
60 |
61 | return data.Items;
62 | }
63 |
--------------------------------------------------------------------------------
/backend/functions/create-candidate/app.js:
--------------------------------------------------------------------------------
1 | const AWS = require("aws-sdk");
2 | var docClient = new AWS.DynamoDB.DocumentClient();
3 | var emailValidator = require("email-validator");
4 | const { v4: uuidv4 } = require("uuid");
5 |
6 | exports.lambdaHandler = async (event, context) => {
7 | let Body = JSON.parse(event.body);
8 | let validationErrors = validateInput(Body);
9 |
10 | if (validationErrors !== null) {
11 | return validationErrors;
12 | }
13 |
14 | let pk = uuidv4();
15 |
16 | var params = {
17 | Item: {
18 | pk: pk,
19 | type: "candidate",
20 | companyId: Body.companyId,
21 | email: Body.email,
22 | firstName: Body.firstName,
23 | lastName: Body.lastName,
24 | createdAt: new Date().toISOString(),
25 | },
26 | ReturnConsumedCapacity: "TOTAL",
27 | TableName: process.env.DB_TABLE,
28 | };
29 |
30 | try {
31 | await docClient.put(params).promise();
32 | return {
33 | statusCode: 200,
34 | headers: {
35 | "Content-Type": "application/json",
36 | "Access-Control-Allow-Origin": "*",
37 | },
38 | body: JSON.stringify({
39 | message: "Candidate created",
40 | data: {
41 | id: pk,
42 | },
43 | }),
44 | };
45 | } catch (error) {
46 | console.error("Error", error.stack);
47 |
48 | return {
49 | statusCode: 500,
50 | headers: {
51 | "Content-Type": "application/json",
52 | "Access-Control-Allow-Origin": "*",
53 | },
54 | body: JSON.stringify({
55 | message: "Candidate creation failed",
56 | error: error.stack,
57 | }),
58 | };
59 | }
60 | };
61 |
62 | function validateInput(body) {
63 | let errors = [];
64 |
65 | if (!emailValidator.validate(body.email)) {
66 | errors.push("Required field email not found or invalid");
67 | }
68 |
69 | if (isNaN(body.companyId)) {
70 | errors.push("Required field companyId not found or invalid");
71 | }
72 |
73 | if (!body.firstName) {
74 | errors.push("Required field first name not found");
75 | }
76 |
77 | if (!body.lastName) {
78 | errors.push("Required field last name not found");
79 | }
80 |
81 | if (errors.length > 0) {
82 | return {
83 | statusCode: 422,
84 | headers: {
85 | "Content-Type": "application/json",
86 | "Access-Control-Allow-Origin": "*",
87 | },
88 | body: JSON.stringify({
89 | message: "Validation errors",
90 | errors: errors,
91 | }),
92 | };
93 | }
94 |
95 | return null;
96 | }
97 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/osx,node,linux,windows
3 |
4 | ### Linux ###
5 | *~
6 |
7 | # temporary files which can be created if a process still has a handle open of a deleted file
8 | .fuse_hidden*
9 |
10 | # KDE directory preferences
11 | .directory
12 |
13 | # Linux trash folder which might appear on any partition or disk
14 | .Trash-*
15 |
16 | # .nfs files are created when an open file is removed but is still being accessed
17 | .nfs*
18 |
19 | ### Node ###
20 | # Logs
21 | logs
22 | *.log
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # Runtime data
28 | pids
29 | *.pid
30 | *.seed
31 | *.pid.lock
32 |
33 | # Directory for instrumented libs generated by jscoverage/JSCover
34 | lib-cov
35 |
36 | # Coverage directory used by tools like istanbul
37 | coverage
38 |
39 | # nyc test coverage
40 | .nyc_output
41 |
42 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
43 | .grunt
44 |
45 | # Bower dependency directory (https://bower.io/)
46 | bower_components
47 |
48 | # node-waf configuration
49 | .lock-wscript
50 |
51 | # Compiled binary addons (http://nodejs.org/api/addons.html)
52 | build/Release
53 |
54 | # Dependency directories
55 | node_modules/
56 | jspm_packages/
57 |
58 | # Typescript v1 declaration files
59 | typings/
60 |
61 | # Optional npm cache directory
62 | .npm
63 |
64 | # Optional eslint cache
65 | .eslintcache
66 |
67 | # Optional REPL history
68 | .node_repl_history
69 |
70 | # Output of 'npm pack'
71 | *.tgz
72 |
73 | # Yarn Integrity file
74 | .yarn-integrity
75 |
76 | # dotenv environment variables file
77 | .env
78 |
79 |
80 | ### OSX ###
81 | *.DS_Store
82 | .AppleDouble
83 | .LSOverride
84 |
85 | # Icon must end with two \r
86 | Icon
87 |
88 | # Thumbnails
89 | ._*
90 |
91 | # Files that might appear in the root of a volume
92 | .DocumentRevisions-V100
93 | .fseventsd
94 | .Spotlight-V100
95 | .TemporaryItems
96 | .Trashes
97 | .VolumeIcon.icns
98 | .com.apple.timemachine.donotpresent
99 |
100 | # Directories potentially created on remote AFP share
101 | .AppleDB
102 | .AppleDesktop
103 | Network Trash Folder
104 | Temporary Items
105 | .apdisk
106 |
107 | ### Windows ###
108 | # Windows thumbnail cache files
109 | Thumbs.db
110 | ehthumbs.db
111 | ehthumbs_vista.db
112 |
113 | # Folder config file
114 | Desktop.ini
115 |
116 | # Recycle Bin used on file shares
117 | $RECYCLE.BIN/
118 |
119 | # Windows Installer files
120 | *.cab
121 | *.msi
122 | *.msm
123 | *.msp
124 |
125 | # Windows shortcuts
126 | *.lnk
127 |
128 | .aws-sam
129 | samconfig.toml
130 | # End of https://www.gitignore.io/api/osx,node,linux,windows
--------------------------------------------------------------------------------
/backend/functions/register-webhook/app.js:
--------------------------------------------------------------------------------
1 | const AWS = require("aws-sdk");
2 | var docClient = new AWS.DynamoDB.DocumentClient();
3 | var validator = require("valid-url");
4 |
5 | exports.lambdaHandler = async (event, context) => {
6 | let Body = JSON.parse(event.body);
7 |
8 | let validationErrors = validateInput(Body);
9 |
10 | if (validationErrors !== null) {
11 | return validationErrors;
12 | }
13 |
14 | let signedToken = gerRandomString(32);
15 | let companyId = Body.companyId;
16 | let eventType = Body.eventType;
17 | let url = Body.url;
18 | let pk = "webhook_" + companyId + "_" + eventType;
19 |
20 | var params = {
21 | Item: {
22 | pk: pk,
23 | type: "webhook",
24 | url: url,
25 | companyId: String(companyId),
26 | signedToken: signedToken,
27 | createdAt: new Date().toISOString(),
28 | },
29 | ReturnConsumedCapacity: "TOTAL",
30 | TableName: process.env.DB_TABLE,
31 | ConditionExpression: "pk <> :pk",
32 | ExpressionAttributeValues: {
33 | ":pk": pk,
34 | },
35 | };
36 |
37 | try {
38 | await docClient.put(params).promise();
39 |
40 | return {
41 | statusCode: 200,
42 | headers: {
43 | "Content-Type": "application/json",
44 | "Access-Control-Allow-Origin": "*",
45 | },
46 | body: JSON.stringify({
47 | message: "Webhook created",
48 | data: {
49 | token: signedToken,
50 | },
51 | }),
52 | };
53 | } catch (error) {
54 | if ((error.code = "ConditionalCheckFailedException")) {
55 | var errorStack = "Record exists.";
56 | } else {
57 | var errorStack = error.stack;
58 | }
59 |
60 | console.error("Error", error.stack);
61 |
62 | return {
63 | statusCode: 500,
64 | headers: {
65 | "Content-Type": "application/json",
66 | "Access-Control-Allow-Origin": "*",
67 | },
68 | body: JSON.stringify({
69 | message: "Webhook creation failed",
70 | error: errorStack,
71 | }),
72 | };
73 | }
74 | };
75 |
76 | function validateInput(body) {
77 | let errors = [];
78 |
79 | if (!validator.isUri(body.url)) {
80 | errors.push("Required field url not found or invalid");
81 | }
82 |
83 | if (!body.companyId || isNaN(body.companyId)) {
84 | errors.push("Required field companyId not found or invalid");
85 | }
86 |
87 | if (!body.eventType) {
88 | errors.push("Required field eventType not found or invalid");
89 | }
90 |
91 | if (errors.length > 0) {
92 | return {
93 | statusCode: 422,
94 | headers: {
95 | "Content-Type": "application/json",
96 | "Access-Control-Allow-Origin": "*",
97 | },
98 | body: JSON.stringify({
99 | message: "Validation errors",
100 | error: errors,
101 | }),
102 | };
103 | }
104 |
105 | return null;
106 | }
107 |
108 | function gerRandomString(length, onlyNumbers = false) {
109 | var result = "";
110 | var characters =
111 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
112 |
113 | if (onlyNumbers === true) {
114 | var characters = "0123456789";
115 | }
116 | var charactersLength = characters.length;
117 |
118 | for (var i = 0; i < length; i++) {
119 | result += characters.charAt(Math.floor(Math.random() * charactersLength));
120 | }
121 |
122 | return result;
123 | }
124 |
--------------------------------------------------------------------------------
/frontend/src/components/CreateWebhook.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
22 |
23 |
24 |
79 |
80 |
138 |
--------------------------------------------------------------------------------
/frontend/src/components/WebhookCallHistory.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 | | Time |
16 | Webhook Url |
17 | Status |
18 | Payload |
19 | Additional Info |
20 |
21 |
22 |
23 |
24 | | {{ webhookCall.createdAt }} |
25 | {{ webhookCall.url }} |
26 | {{ webhookCall.status }} |
27 | {{ webhookCall.payload }} |
28 | {{ webhookCall.output !== "{}" ? webhookCall.output : '-' }} |
29 |
30 |
31 |
32 | No data found. |
33 |
34 |
35 |
36 |
37 |
38 |
91 |
92 |
154 |
--------------------------------------------------------------------------------
/frontend/src/components/CreateCandidate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
29 |
30 |
31 |
32 |
93 |
94 |
148 |
--------------------------------------------------------------------------------
/backend/statemachine/webhook_management.asl.json:
--------------------------------------------------------------------------------
1 | {
2 | "Comment": "State machine for webhok management service",
3 | "StartAt": "Get Webhooks For Company And Event",
4 | "States": {
5 | "Get Webhooks For Company And Event": {
6 | "Type": "Task",
7 | "Resource": "arn:aws:states:::dynamodb:getItem",
8 | "Parameters": {
9 | "TableName": "${DynamoDBTableName}",
10 | "Key": {
11 | "pk": {
12 | "S.$": "States.Format('webhook_{}_{}', $.companyId, $.webhookEvent)"
13 | },
14 | "type": {
15 | "S": "webhook"
16 | }
17 | }
18 | },
19 | "Next": "Validate If Webhook Data Exists",
20 | "ResultPath": "$.webhookData"
21 | },
22 | "Validate If Webhook Data Exists": {
23 | "Type": "Choice",
24 | "Choices": [
25 | {
26 | "And": [
27 | {
28 | "Variable": "$.webhookData.Item",
29 | "IsPresent": true
30 | },
31 | {
32 | "Variable": "$.webhookData.Item.pk.S",
33 | "IsPresent": true
34 | },
35 | {
36 | "Variable": "$.webhookData.Item.signedToken.S",
37 | "IsPresent": true
38 | },
39 | {
40 | "Variable": "$.webhookData.Item.url.S",
41 | "IsPresent": true
42 | }
43 | ],
44 | "Next": "Transform Webhook Data"
45 | }
46 | ],
47 | "Default": "SkipExecution"
48 | },
49 | "Transform Webhook Data": {
50 | "Type": "Pass",
51 | "Next": "Fetch Resource Data Lambda",
52 | "Parameters": {
53 | "webhookId.$": "$.webhookData.Item.pk.S",
54 | "webhookSignToken.$": "$.webhookData.Item.signedToken.S",
55 | "webhookUrl.$": "$.webhookData.Item.url.S",
56 | "companyId.$": "$.companyId",
57 | "resourceType.$": "$.resourceType",
58 | "resourceId.$": "$.resourceId"
59 | }
60 | },
61 | "Fetch Resource Data Lambda": {
62 | "Type": "Task",
63 | "Resource": "arn:aws:states:::lambda:invoke",
64 | "Parameters": {
65 | "FunctionName": "${FetchResourceFunctionName}",
66 | "Payload": {
67 | "key.$": "$.resourceId",
68 | "type.$": "$.resourceType"
69 | }
70 | },
71 | "Retry": [
72 | {
73 | "ErrorEquals": [
74 | "Lambda.ServiceException",
75 | "Lambda.AWSLambdaException",
76 | "Lambda.SdkClientException"
77 | ],
78 | "IntervalSeconds": 2,
79 | "MaxAttempts": 6,
80 | "BackoffRate": 2
81 | }
82 | ],
83 | "Next": "Validate If Event Object Data Exists",
84 | "ResultPath": "$.taskResult",
85 | "ResultSelector": {
86 | "resourceData.$": "$.Payload"
87 | }
88 | },
89 | "Validate If Event Object Data Exists": {
90 | "Type": "Choice",
91 | "Choices": [
92 | {
93 | "Not": {
94 | "Variable": "$.taskResult.resourceData",
95 | "IsNull": true
96 | },
97 | "Next": "Create Webhook Call"
98 | }
99 | ],
100 | "Default": "Fail"
101 | },
102 | "Create Webhook Call": {
103 | "Type": "Task",
104 | "Resource": "arn:aws:states:::lambda:invoke",
105 | "Parameters": {
106 | "Payload.$": "$",
107 | "FunctionName": "${CreateWebhookCallFunctionName}"
108 | },
109 | "Next": "Queue Webhook Call",
110 | "ResultPath": "$.webhookCallData",
111 | "ResultSelector": {
112 | "id.$": "$.Payload.id",
113 | "payload.$": "$.Payload.payload"
114 | }
115 | },
116 | "Queue Webhook Call": {
117 | "Type": "Task",
118 | "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
119 | "Parameters": {
120 | "QueueUrl": "${SQSQueueUrl}",
121 | "MessageBody": {
122 | "url.$": "$.webhookUrl",
123 | "webhookCallId.$": "$.webhookCallData.id",
124 | "signingToken.$": "$.webhookSignToken",
125 | "payload.$": "$.webhookCallData.payload",
126 | "taskToken.$": "$$.Task.Token"
127 | }
128 | },
129 | "HeartbeatSeconds": 3600,
130 | "Retry": [
131 | {
132 | "ErrorEquals": [
133 | "States.ALL"
134 | ],
135 | "BackoffRate": 2,
136 | "IntervalSeconds": 60,
137 | "MaxAttempts": 2
138 | }
139 | ],
140 | "Next": "Update WebhookCall",
141 | "ResultPath": "$.webhookCallResult",
142 | "Catch": [
143 | {
144 | "ErrorEquals": [
145 | "States.ALL"
146 | ],
147 | "Next": "Transform Error",
148 | "ResultPath": "$.webhookCallResult"
149 | }
150 | ]
151 | },
152 | "Transform Error": {
153 | "Type": "Pass",
154 | "Next": "Update WebhookCall",
155 | "ResultPath": "$.webhookCallResult",
156 | "Parameters": {
157 | "status": "failed",
158 | "payload": "",
159 | "output": {
160 | "Error.$": "$.webhookCallResult.Error",
161 | "Cause.$": "$.webhookCallResult.Cause"
162 | }
163 | }
164 | },
165 | "Update WebhookCall": {
166 | "Type": "Task",
167 | "Resource": "arn:aws:states:::dynamodb:updateItem",
168 | "Parameters": {
169 | "TableName": "${DynamoDBTableName}",
170 | "Key": {
171 | "pk": {
172 | "S.$": "$.webhookCallData.id"
173 | },
174 | "type": {
175 | "S": "webhookcall"
176 | }
177 | },
178 | "UpdateExpression": "SET #status = :status, #output = :output",
179 | "ExpressionAttributeValues": {
180 | ":status": {
181 | "S.$": "$.webhookCallResult.status"
182 | },
183 | ":output": {
184 | "S.$": "States.JsonToString($.webhookCallResult.output)"
185 | }
186 | },
187 | "ExpressionAttributeNames": {
188 | "#status": "status",
189 | "#output": "output"
190 | }
191 | },
192 | "End": true
193 | },
194 | "Fail": {
195 | "Type": "Fail",
196 | "Error": "dataNotFound",
197 | "Cause": "Required resource data not exists in dynamodb"
198 | },
199 | "SkipExecution": {
200 | "Type": "Pass",
201 | "End": true,
202 | "Comment": "Webhook not configured for this event"
203 | }
204 | }
205 | }
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 | # backend
2 |
3 | This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders:
4 |
5 | - functions - Code for the application's Lambda functions to check the value of, buy, or sell shares of a stock.
6 | - statemachines - Definition for the state machine that orchestrates the stock trading workflow.
7 | - template.yaml - A template that defines the application's AWS resources.
8 |
9 | This application creates a mock stock trading workflow which runs on a pre-defined schedule (note that the schedule is disabled by default to avoid incurring charges). It demonstrates the power of Step Functions to orchestrate Lambda functions and other AWS resources to form complex and robust workflows, coupled with event-driven development using Amazon EventBridge.
10 |
11 | AWS Step Functions lets you coordinate multiple AWS services into serverless workflows so you can build and update apps quickly. Using Step Functions, you can design and run workflows that stitch together services, such as AWS Lambda, AWS Fargate, and Amazon SageMaker, into feature-rich applications.
12 |
13 | The application uses several AWS resources, including Step Functions state machines, Lambda functions and an EventBridge rule trigger. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code.
14 |
15 | If you prefer to use an integrated development environment (IDE) to build and test the Lambda functions within your application, you can use the AWS Toolkit. The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started:
16 |
17 | * [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html)
18 | * [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html)
19 | * [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html)
20 | * [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html)
21 | * [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html)
22 | * [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html)
23 | * [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html)
24 | * [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html)
25 | * [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html)
26 | * [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html)
27 | * [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html)
28 |
29 | The AWS Toolkit for VS Code includes full support for state machine visualization, enabling you to visualize your state machine in real time as you build. The AWS Toolkit for VS Code includes a language server for Amazon States Language, which lints your state machine definition to highlight common errors, provides auto-complete support, and code snippets for each state, enabling you to build state machines faster.
30 |
31 | ## Deploy the sample application
32 |
33 | The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda.
34 |
35 | To use the SAM CLI, you need the following tools:
36 |
37 | * SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
38 | * Node.js - [Install Node.js 14](https://nodejs.org/en/), including the NPM package management tool.
39 | * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community)
40 |
41 | To build and deploy your application for the first time, run the following in your shell:
42 |
43 | ```bash
44 | sam build
45 | sam deploy --guided
46 | ```
47 |
48 | The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts:
49 |
50 | * **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name.
51 | * **AWS Region**: The AWS region you want to deploy your app to.
52 | * **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes.
53 | * **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command.
54 | * **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application.
55 |
56 | You can find your State Machine ARN in the output values displayed after deployment.
57 |
58 | ## Use the SAM CLI to build and test locally
59 |
60 | Build the Lambda functions in your application with the `sam build --use-container` command.
61 |
62 | ```bash
63 | backend$ sam build
64 | ```
65 |
66 | The SAM CLI installs dependencies defined in `functions/*/package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder.
67 |
68 | ## Add a resource to your application
69 | The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types.
70 |
71 | ## Fetch, tail, and filter Lambda function logs
72 |
73 | To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug.
74 |
75 | `NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM.
76 |
77 | ```bash
78 | backend$ sam logs -n StockCheckerFunction --stack-name backend --tail
79 | ```
80 |
81 | You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html).
82 |
83 | ## Unit tests
84 |
85 | Tests are defined in the `functions/*/tests` folder in this project. Use NPM to install the [Mocha test framework](https://mochajs.org/) and run unit tests.
86 |
87 | ```bash
88 | backend$ cd functions/stock-checker
89 | stock-checker$ npm install
90 | stock-checker$ npm run test
91 | ```
92 |
93 | ## Cleanup
94 |
95 | To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following:
96 |
97 | ```bash
98 | aws cloudformation delete-stack --stack-name backend
99 | ```
100 |
101 | ## Resources
102 |
103 | See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts.
104 |
105 | Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/)
106 |
--------------------------------------------------------------------------------
/backend/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: "2010-09-09"
2 | Transform: AWS::Serverless-2016-10-31
3 | Description: >
4 | webhook management backend
5 |
6 | SAM Template for webhook management system
7 |
8 | Parameters:
9 | Stage:
10 | Type: String
11 | Default: 'dev'
12 | eventSource:
13 | Type: String
14 | Default: XYZCo
15 | EventBusName:
16 | Type: String
17 | Default: EventBusXYZCompany
18 |
19 | Resources:
20 | EventBus:
21 | Type: AWS::Events::EventBus
22 | Properties:
23 | Name: !Ref EventBusName
24 |
25 | SQSQueue:
26 | Type: AWS::SQS::Queue
27 | Properties:
28 | VisibilityTimeout: 60
29 |
30 | ApiGatewayApi:
31 | Type: AWS::Serverless::Api
32 | Properties:
33 | StageName: !Ref Stage
34 | Cors:
35 | AllowMethods: "'OPTIONS,POST,GET'"
36 | AllowHeaders: "'Content-Type'"
37 | AllowOrigin: "'*'"
38 |
39 | DynamodbTable:
40 | Type: AWS::DynamoDB::Table
41 | Properties:
42 | AttributeDefinitions:
43 | - AttributeName: pk
44 | AttributeType: S
45 | - AttributeName: type
46 | AttributeType: S
47 | - AttributeName: companyId
48 | AttributeType: S
49 | KeySchema:
50 | - AttributeName: pk
51 | KeyType: HASH
52 | - AttributeName: type
53 | KeyType: RANGE
54 | GlobalSecondaryIndexes:
55 | - IndexName: gsiTypeAndCompanyId
56 | KeySchema:
57 | - AttributeName: type
58 | KeyType: HASH
59 | - AttributeName: companyId
60 | KeyType: RANGE
61 | Projection:
62 | ProjectionType: INCLUDE
63 | NonKeyAttributes:
64 | - pk
65 | - createdAt
66 | - payload
67 | - status
68 | - url
69 | - output
70 | ProvisionedThroughput:
71 | ReadCapacityUnits: 1
72 | WriteCapacityUnits: 1
73 | ProvisionedThroughput:
74 | ReadCapacityUnits: 1
75 | WriteCapacityUnits: 1
76 | StreamSpecification:
77 | StreamViewType: NEW_IMAGE
78 |
79 | RegisterWebhookFunction:
80 | Type: AWS::Serverless::Function
81 | Description: Webhook registration function
82 | Properties:
83 | CodeUri: functions/register-webhook/
84 | Handler: app.lambdaHandler
85 | Runtime: nodejs14.x
86 | Timeout: 3
87 | Environment:
88 | Variables:
89 | DB_TABLE: !Ref DynamodbTable
90 | Policies:
91 | - DynamoDBWritePolicy:
92 | TableName: !Ref DynamodbTable
93 | Events:
94 | CreateWebhookApi:
95 | Type: Api
96 | Properties:
97 | Path: /webhooks
98 | Method: post
99 | RestApiId: !Ref ApiGatewayApi
100 |
101 | CreateCandidateFunction:
102 | Type: AWS::Serverless::Function
103 | Description: Create canidate function
104 | Properties:
105 | CodeUri: functions/create-candidate/
106 | Handler: app.lambdaHandler
107 | Runtime: nodejs14.x
108 | Timeout: 10
109 | Environment:
110 | Variables:
111 | DB_TABLE: !Ref DynamodbTable
112 | Policies:
113 | - DynamoDBWritePolicy:
114 | TableName: !Ref DynamodbTable
115 | Events:
116 | CreateCandidateApi:
117 | Type: Api
118 | Properties:
119 | Path: /candidates
120 | Method: post
121 | RestApiId: !Ref ApiGatewayApi
122 |
123 | FetchResourceFunction:
124 | Type: AWS::Serverless::Function
125 | Description: Fetch resource data from Dyanamodb
126 | Properties:
127 | CodeUri: functions/fetch-resource-data/
128 | Handler: app.lambdaHandler
129 | Runtime: nodejs12.x
130 | Timeout: 10
131 | Environment:
132 | Variables:
133 | DB_TABLE: !Ref DynamodbTable
134 | Policies:
135 | - DynamoDBReadPolicy:
136 | TableName: !Ref DynamodbTable
137 |
138 | TriggerResourceCreatedFunction:
139 | Type: AWS::Serverless::Function
140 | Description: Put resource data to event bus when record added to Dyanamodb
141 | Properties:
142 | CodeUri: functions/trigger-resource-created/
143 | Handler: app.lambdaHandler
144 | Runtime: nodejs12.x
145 | Timeout: 20
146 | Environment:
147 | Variables:
148 | EVENT_BUS: !Ref EventBusName
149 | EVENT_SOURCE: !Ref eventSource
150 | Policies:
151 | - EventBridgePutEventsPolicy:
152 | EventBusName: !Ref EventBusName
153 | Events:
154 | DynamodbTableStream:
155 | Type: DynamoDB
156 | Properties:
157 | Stream: !GetAtt DynamodbTable.StreamArn
158 | StartingPosition: TRIM_HORIZON
159 | BatchSize: 100
160 | FilterCriteria:
161 | Filters:
162 | - Pattern: "{\"eventName\":[\"INSERT\"],\"dynamodb\":{\"NewImage\":{\"type\":{\"S\":[\"candidate\"]}}}}"
163 |
164 | CreateWebhookCallFunction:
165 | Type: AWS::Serverless::Function
166 | Description: Create webhook call with status pending
167 | Properties:
168 | CodeUri: functions/create-webhook-call/
169 | Handler: app.lambdaHandler
170 | Runtime: nodejs12.x
171 | Timeout: 10
172 | EventInvokeConfig:
173 | MaximumRetryAttempts: 0
174 | Environment:
175 | Variables:
176 | DB_TABLE: !Ref DynamodbTable
177 | Policies:
178 | - DynamoDBWritePolicy:
179 | TableName: !Ref DynamodbTable
180 |
181 | CallWebhookFunction:
182 | Type: AWS::Serverless::Function
183 | Description: Call webhook with the resource data
184 | Properties:
185 | CodeUri: functions/call-webhook/
186 | Handler: app.lambdaHandler
187 | Runtime: nodejs12.x
188 | Timeout: 60
189 | EventInvokeConfig:
190 | MaximumRetryAttempts: 0
191 | Policies:
192 | - Statement:
193 | - Sid: StateStatusPermission
194 | Effect: Allow
195 | Action:
196 | - states:SendTaskSuccess
197 | - states:SendTaskFailure
198 | Resource: '*'
199 | Events:
200 | SQSEvent:
201 | Type: SQS
202 | Properties:
203 | Queue: !GetAtt SQSQueue.Arn
204 | BatchSize: 10
205 |
206 | FetchWebhookHistoryFunction:
207 | Type: AWS::Serverless::Function
208 | Description: Fetcho webhook call history by companyId
209 | Properties:
210 | CodeUri: functions/fetch-webhook-history/
211 | Handler: app.lambdaHandler
212 | Runtime: nodejs14.x
213 | Timeout: 10
214 | Environment:
215 | Variables:
216 | DB_TABLE: !Ref DynamodbTable
217 | Policies:
218 | - DynamoDBReadPolicy:
219 | TableName: !Ref DynamodbTable
220 | Events:
221 | CreateCandidateApi:
222 | Type: Api
223 | Properties:
224 | Path: /webhook_history/{companyId}
225 | Method: get
226 | RestApiId: !Ref ApiGatewayApi
227 |
228 | WebhookManagmentStateMachine:
229 | Type: AWS::Serverless::StateMachine
230 | Properties:
231 | DefinitionUri: statemachine/webhook_management.asl.json
232 | DefinitionSubstitutions:
233 | DynamoDBTableName: !Ref DynamodbTable
234 | FetchResourceFunctionName: !Ref FetchResourceFunction
235 | SQSQueueUrl: !Ref SQSQueue
236 | CreateWebhookCallFunctionName: !Ref CreateWebhookCallFunction
237 | Events:
238 | EventBridgeRule:
239 | Type: EventBridgeRule
240 | Properties:
241 | EventBusName: !Ref EventBus
242 | Pattern:
243 | source:
244 | - !Ref eventSource
245 | detail-type:
246 | - candidate.created
247 | InputPath: $.detail
248 | Policies:
249 | - LambdaInvokePolicy:
250 | FunctionName: !Ref FetchResourceFunction
251 | - LambdaInvokePolicy:
252 | FunctionName: !Ref CreateWebhookCallFunction
253 | - SQSSendMessagePolicy:
254 | QueueName: !GetAtt SQSQueue.QueueName
255 | - DynamoDBReadPolicy:
256 | TableName: !Ref DynamodbTable
257 | - DynamoDBWritePolicy:
258 | TableName: !Ref DynamodbTable
259 |
260 | Outputs:
261 | DynamoDBTable:
262 | Description: "Dynamodb Table Arn"
263 | Value: !GetAtt DynamodbTable.Arn
264 | ApiBaseUrl:
265 | Description: "Base Url"
266 | Value: !Sub "https://${ApiGatewayApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/"
267 | EventBus:
268 | Description: "Event Bus Arn"
269 | Value: !GetAtt EventBus.Arn
270 | FetchResourceFunction:
271 | Description: FetchResourceFunction function name
272 | Value: !Ref FetchResourceFunction
273 | SQSQueueUrl:
274 | Description: SQS Queue URL
275 | Value: !Ref SQSQueue
276 | WebhookManagmentStateMachineName:
277 | Description: Webhook Managment State Machine Name
278 | Value: !Ref WebhookManagmentStateMachine
279 |
--------------------------------------------------------------------------------