Processing Webhooks with Stream Chat, Lambda, and Node.js

Stream Chat makes it extremely easy to integrate chat into any application. In this tutorial, we will explore what webhooks are, how they work, and how Stream makes for a seamless experience to carry out many other actions on the backend simultaneously. For example, you could send an email, update your database, and so forth.

Lanre A.
Lanre A.
Published January 23, 2020 Updated January 28, 2020

In this tutorial, we will show you how to process webhooks to send a message to a Slack channel! Here at Stream, we are big fans of Chat. With our chat API, we offer a webhook so that you can relay data to an endpoint of your choice. Webhooks are an excellent way to make use of messages and other activities create additional functionality.

In this post, we'll detail how you can process webhooks with Stream Chat, AWS Lambda, and Node.js.

What Are Webhooks?

Webhooks are one of the most popular ways to send events between two applications, over the internet. In this tutorial, whenever a user joins a chatroom, Stream will use webhooks to send an HTTP POST request to a URL that we will have configured.

Prerequisites

  • Ngrok: Stream needs to be able to successfully send the POST request to a given URL, which we will define. Because we need a real URL, localhost isn't going to cut it. Visit https://ngrok.com to get started.
  • A Stream account. Visit the dashboard to get started!
  • An understanding of NodeJS
  • React Native installed

This application will consist of two parts:

  • A server (the backend): this is going to be a serverless application deployed on AWS.
  • A client (the frontend): this is going to be built in React Native.

Both of them will live in the same directory. Create this directory using the following command:

mkdir lambda-stream-chat-webhooks

Creating a Stream Application

Before proceeding with the tutorial, you will need to get your credentials from Stream Chat. Once you’ve created your account, you will need to copy the credentials as depicted in the image below and save them somewhere safe, as they will be used to build the server.

  • Go to the Stream Dashboard
  • Create a new application
  • Click on Chat in the top navigation
  • Scroll down to "App Access Keys" to view your credentials.

Creating a Slack Application

As mentioned earlier, whenever a webhook is processed, we are going to post a message in Slack. To accomplish this, you will need to visit the Apps page in Slack. Click on the New App button, which will display a modal like that in the screenshot below. Once you've completed the form correctly, push the Create App button to continue.

Once the app has been created, you will need to create a new Incoming webhook. This will require you to select a channel where the messages will be posted:

Make sure to copy and store the generated URL, as you will need it later!

Building the Server

Since we are going to make use of the serverless paradigm, the first step is to install the serverless CLI. That can be done as follows:

npm install -g serverless
serverless create --template aws-nodejs --path server
cd server
touch package.json

As you notice in the previous code block, we created a new file, the package.json. It contains the dependencies we need for our application:

{
  "name": "server-implementation",
  "version": "1.0.0",
  "description": "",
  "main": "handler.js",
  "dependencies": {
    "@slack/webhook": "^5.0.2",
    "body-parser": "^1.19.0",
    "express": "^4.17.1",
    "serverless": "^1.61.2",
    "serverless-http": "^2.3.0",
    "serverless-offline": "^5.12.1",
    "stream-chat": "^1.2.2"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Once this is complete, the next step is to install these packages by running npm install!

After the installations are complete, we will create another file called handler.js, which contains our implementation for the server:

'use strict';

const serverless = require('serverless-http');
const express = require('express');
const bodyParser = require('body-parser');
const StreamChat = require('stream-chat').StreamChat;

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

const client = new StreamChat(process.env.API_KEY, process.env.API_SECRET);

const channel = client.channel('messaging', 'lambda-webhook-chat');
channel.create();

app.post('/users/create', (req, res) => {
  const username = req.body.username;

  if (username === undefined || username.length == 0) {
    res.status(400).send({
      status: false,
      message: 'Please provide your username',
    });
    return;
  }

  res.status(200);
  res.send({
    status: true,
    message: 'You have been successfully authenticated',
    token: client.createToken(username),
    user_id: username,
  });
});

app.post('/users/add_member', (req, res) => {
  const username = req.body.username;

  if (username === undefined || username.length == 0) {
    res.status(400).send({
      status: false,
      message: 'Please provide your username',
    });
    return;
  }

  channel
    .addMembers([username])
    .then(() => {
      res.status(200).send({ status: true });
    })
    .catch(err => {
      console.log(err);
      res.status(200).send({ status: false });
    });
});

module.exports.hello = serverless(app);

We will also need to create the Slack handler for the webhook to be called by Stream Chat. To do that, you will need to create a new file called slack.js. In this newly created file, you will need to copy and paste in the following content:

const { IncomingWebhook } = require('@slack/webhook');

const webhook = new IncomingWebhook(process.env.SLACK_WEBHOOK_URL);

module.exports.slack = async (event, context, callback) => {
  const data = JSON.parse(event.body);

  if (data.type === 'message.new') {
    await webhook.send({
      text: 'A new message was sent',
    });
  }

  callback(null, {
    statusCode: 201,
    headers: { 'Content-Type': 'text/json' },
    body: JSON.stringify({ status: true }),
  });
};

In the above, we created three HTTP routes:

  • users/create: This route creates an access token for the user.
  • users/add_member: This route adds a given member to the chatroom with channel_member permissions. Without this, the user would not be able to create or add new messages. You can take a look at the permission chart here.
  • hook: This route contains the logic of sending a new message to the Slack workspace.

You’ll also note we make a lot of references to process.env; this is because we need to store a few configuration values in the environment! To do that, you will need to make a few changes to your serverless.yml file that was generated earlier:

Building your own app? Get early access to our Livestream or Video Calling API and launch in days!
provider:
  name: aws
  runtime: nodejs12.x
  environment:
    API_KEY: API_KEY_FROM_DASHBOARD
    API_SECRET: API_SECRET_FROM_DASHBOARD
    SLACK_WEBHOOK_URL: SLACK_HTTP_WEBHOOK_URL

The final step is to update the serverless.yml file again with the HTTP routes:

functions:
  hello:
    handler: handler.hello
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
    events:
      - http:
          path: users/create
          method: post

      - http:
          path: users/add_member
          method: post

  slack:
    handler: slack.slack
    events:
      - http:
          path: /hook
          method: post

Once we’ve done all of this, we can deploy this serverless function to AWS with the following command:

$ serverless deploy -v

Note: If you have not previously set up serverless with your AWS, you will need to run serverless config credentials --provider AWS --key IAM_USER_KEY --secret IAM_USER_SECRET. Please refer to the AWS docs for more information.

Running the above deploy script should output a whole bunch of information, but the only thing you should be concerned with is the URL you'll need to invoke your serverless functions.

Adding Your Webhook Address to the Stream Chat Dashboard

The final step to connecting all your work is to add your webhook URL to the Stream Chat dashboard! The easiest way to do this is via the dashboard. You will need to do the following:

  • Go to the Stream Dashboard
  • Create a new application
  • Click on Chat in the top navigation
  • Scroll down to "Webhook" to provide the address the deploy script provided. It should be something like https://XYZ.us-east-1.amazonaws.com/dev/hook.
  • Click "Save".

Building the Client

As discussed earlier, the client will be a React Native application. We will also be making use of the SDK that Stream Chat provides to simplify building the application!

The first step is to create a new project; this should be inside the lambda-stream-chat-webhooks directory that we created earlier:

$ react-native init client
 ## or 
$ npx  react-native init client

After this is done, we will need to build the actual app... Our app is going to consist of 2 screens:

  • The home screen (this doubles as the login screen, too).
  • The chat screen.

We will start with the chat screen! You will need to create a new file called ChatView.js. You can do this with the following command:

$ touch ChatView.js

In the newly created file, you will need to copy and paste the following code:

import React, {Component} from 'react';
import {SafeAreaView, View} from 'react-native';
import {
  Chat,
  Channel,
  MessageList,
  MessageInput,
} from 'stream-chat-react-native';

export default class ChatView extends Component {
  state = {
    isReady: false,
  };

  render() {
    const channel = this.props.chatClient.channel(
      'messaging',
      'lambda-webhook-chat',
      {
        name: 'Webhook server',
      }
    );

    return (
      <Chat client={this.props.chatClient}>
        <Channel channel={channel}>
          <View style={{display: 'flex', height: '100%'}}>
            <MessageList />
            <MessageInput />
          </View>
        </Channel>
      </Chat>
    );
  }
}

The next step is to create the home screen, which doubles as the login screen! To do this, we’ll utilize the App.js file created by the react-native init script. You will need to copy the following code into this App.js file:

import React, {Fragment, Component} from 'react';
import {StreamChat} from 'stream-chat';
import axios from 'axios';
import {
  TouchableOpacity,
  Text,
  Alert,
  Button,
  TextInput,
  View,
  StyleSheet,
} from 'react-native';
import ChatView from './ChatView';

export default class App extends Component {
  state = {
    username: '',
    isAuthenticated: false,
  };

  constructor(props) {
    super(props);

    this.chatClient = new StreamChat('STREAMCHAT_API_KEY');
  }

  handleSignin = () => {
    if (this.state.username === '') {
      Alert.alert('Username', 'Please provide your username');
      return;
    }

    axios
      .post('SERVERLESS_FUNCTION_AWS_ENDPOINT_THAT_ENDS_WITH_CREATE', {
        username: this.state.username,
      })
      .then(res => {
        if (res.data.status) {
          this.chatClient
            .setUser(
              {
                id: res.data.user_id,
                username: this.state.username,
                image:
                  'https://stepupandlive.files.wordpress.com/2014/09/3d-animated-frog-image.jpg',
              },
              res.data.token
            )
            .then(() => {
              axios
                .post('SERVERLESS_FUNCTION_AWS_ENDPOINT_THAT_ENDS_WITH_ADD_MEMBER', {
                  username: this.state.username,
                })
                .then(() => {
                  this.setState({isAuthenticated: true});
                });
            });
          return;
        }

        Alert.alert('Authentication', 'Could not authenticate you. Try again');
      })
      .catch(err => {
        console.log(err);
        Alert.alert(
          'Authentication',
          'An error occurred while authenticating you. Try again'
        );
      });
  };

  render() {
    if (this.state.isAuthenticated) {
      return (
        <ChatView chatClient={this.chatClient} username={this.state.username} />
      );
    }
    return (
      <View style={styles.container}>
        <Fragment>
          <TextInput
            style={styles.input}
            value={this.state.username}
            onChangeText={username => this.setState({username})}
            placeholder={'Please provide your username'}
            style={styles.input}
          />
          <TouchableOpacity style={styles.button} onPress={this.handleSignin}>
            <Text style={{textAlign: 'center', height: 20}}>Login</Text>
          </TouchableOpacity>
        </Fragment>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#ecf0f1',
  },
  input: {
    height: 40,
    width: '100%',
    borderColor: 'silver',
    borderBottomWidth: StyleSheet.hairlineWidth,
    marginBottom: 20,
  },
  button: {
    width: '100%',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'orange',
    marginBottom: 12,
    paddingVertical: 12,
    borderRadius: 4,
    borderWidth: StyleSheet.hairlineWidth,
    borderColor: 'red',
  },
});

Note: Please remember to update STREAMCHAT_API_KEY to the actual key you copied from the Stream Chat dashboard.

Once complete, you can spin up the app by running the following command:

$ react-native run-ios
$ react-native run-android

A simulator/emulator should automatically open and the expected result should be similar to the gif below:

Last of all, take a look at the Slack channel you have configured the webhook with. You should see "A new message was sent" every time a user sends a new message in the chat application!

Conclusion

In this tutorial, we made use of Serverless technology, Stream Chat, and AWS Lambda to build a fully functional chat application that sends a notification to Slack every time a user uses the app. You can use this same strategy to create notifications for many other chat actions OR to connect your other app functions via webhooks!

You can find the full source code on GitHub.

Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->