Write a Chatbot in Swift and Deploy to AWS Lambda

Matheus C.
Matheus C.
Published August 4, 2020 Updated March 22, 2021

Did you know you can use Swift in the backend to build a chatbot and deploy it to AWS? We've recently published an Open Source project called Swift Lambda to make the process easier.

In this tutorial, we'll use Swift Lambda to build a chatbot that can reply to user messages automatically using Stream's powerful chat API.

Animation shows a Swift Lambda being deployed and a chatbot interaction happening in iPhone simulator

The full code for this tutorial can be found in the Samples folder inside the Swift Lambda repository

Requirements

Set the Webhook URL

After you've set up your iOS chat application and your Swift Lambda is up and running, you need to configure the Webhook URL in the Stream Chat dashboard.

Screenshot shows Webhook URL is configured in the Stream Chat dashboard

As you can learn in the webhooks docs, every event will generate a POST request to the provided endpoint, which is where our Swift Lambda is running.

Configure Swift Lambda

Initially, Swift Lambda is set up to handle GET requests. We need to change it to process POST requests, which can be done by editing the serverless.yml file and swapping the line method: get with method: post.

Parse the request data

When the POST request hits your lambda, it will contain a JSON object describing the event. For this chatbot, we're only interested in the message.new event. To see which fields you can expect in this event, see the documentation.

In the main.swift file, paste the code below.

import AWSLambdaEvents
import AWSLambdaRuntime
import NIO

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

Lambda.run { (context: Lambda.Context, event: APIGateway.Request, callback: @escaping (Result<APIGateway.Response, Error>) -> Void) in
    context.logger.debug("hello, api gateway!")
    
    let botId = "Agent"
    
    guard
        let bodyData = event.body?.data(using: .utf8),
        let json = try? JSONSerialization.jsonObject(with: bodyData, options: []) as? [String: Any],
        json["type"] as? String == "message.new",
        let channelType = json["channel_type"] as? String,
        let channelId = json["channel_id"] as? String,
        let message = json["message"] as? [String: Any],
        let user = message["user"] as? [String: Any],
        let userId = user["id"] as? String,
        userId != botId
    else {
        callback(.success(APIGateway.Response(statusCode: .ok, body: "nope!")))
        return
    }
    
    botReply(channelType: channelType, channelId: channelId, botId: botId) {
        callback(.success(APIGateway.Response(statusCode: .ok)))
    }
}

That code will parse the JSON body into a dictionary to extract the parameters we need: the channel type and id in which the event happened. We also check if it's a "message.new" event and if the id is not the same as the bot. The last check will prevent an infinite loop of sending a message as the bot and receiving an event for that new message.

We'll define the botReply function in the next step.

Send reply to chat

Now that we're aware of when a message is sent to a channel, and we know it's not a message from the bot itself, we can trigger a response from the bot. To post a message, we'll use Stream Chat's REST API endpoint for sending messages.

func botReply(channelType: String, channelId: String, botId: String, completion: @escaping () -> Void) {
    let apiKey = "[insert_your_api_key_here]"
    let jwt = "[insert_your_jwt_here]"
    
    let response = [
        "I am a bot",
        "Beep boop",
        "Having trouble?",
        "I can help"
    ].randomElement() ?? ""
    
    let url = URL(string: "https://chat-us-east-1.stream-io-api.com/channels/\(channelType)/\(channelId)/message?api_key=\(apiKey)")!
    var request = URLRequest(url: url)
    request.allHTTPHeaderFields = ["Authorization": jwt, "stream-auth-type": "jwt"]
    
    request.httpMethod = "POST"
    request.httpBody =
    """
        {
          "message" : {
              "text" : "\(response)",
              "silent" : false,
              "user_id": "\(botId)"
          }
        }
    """.data(using: .utf8)
    
    let task = URLSession.shared.dataTask(with: request) { _,_,_ in
        completion()
    }
    
    task.resume()
}

That code accesses the REST API using the good old URLSession. Just remember to include the imports from the previous snippet and replace the API key and JWT. You can generate a JWT with your secret in the jwt.io site.

Deploying and testing the chatbot

One of the main perks of using Swift Lambda is that it makes iteration fast and easy. After writing your chatbot code, all you need to do is run the ./Scripts/deploy.sh script again and wait a few seconds.

After the deployment is done, you can run your chat app and type something in a channel with the bot.

Image shows a chat UI running on iPhone simulator with a user and a bot chatting

Next steps with the chatbot

This chatbot is very simple and made to demonstrate the usage of Swift Lambda to access the Stream Chat API. However, by pairing Stream's powerful chat features with AI services such as Google's Dialogflow or Amazon's Lex, you can provide your users with a beneficial AI chat experience in the shortest amount of development time.

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