Build a Stock Quote WhatsApp Bot with Java and Twilio

March 31, 2021
Written by
Liz Moy
Twilion
Reviewed by

Build a Stock Quote WhatsApp Bot with Java and Twilio

Photo courtesy of Pixabay (CC BY 2.0)

Whether you are a seasoned investor, downloaded RobinHood when the r/wallstreetbets events overtook the news cycle, or are keen to play with a new API, you can never have too many methods of checking the latest stock prices. You will build a bot for WhatsApp that will message a user basic stock quote information when they send in a valid ticker symbol.

You’ll use the Finnhub.io Stock API to do so. They also have a Forex and Crypto API, so check those out if you want to add on to what you will build here.

Prerequisites

  • A Twilio Account - sign up for a free one here and get $10 when you upgrade your account
  • Java 8 or above (for simpler installation, check out SDKMAN! This tutorial uses version 11.0.10.hs-adpt.
  • API Key for the Finnhub.io API -  Register for a free one here.
  • ngrok - download it here

You have to sign up for a Finnhub.io account in order to get an API key. If you’d rather go with an option that doesn’t require sign up, you can opt for using something like the Yahoo Finance API. Onward!

Set up the Java project

Use the Spring Initializr to set up your project by clicking this link, which will fill in the details of the configuration for you. Click “Generate” and then import the project into your IDE — I am using IntelliJ IDEA. (And yes, the project is named “stonks” here because, well, stonks).

Create a controller to handle messaging

Next you will set up a Controller so that you can make an endpoint to add into the Programmable Messaging configuration in the Twilio Console.

If you open src/main/java/com/whatsappstonksbot/whatsappbot you will see that Spring generates a class that will start-up the application. You don’t need to change anything in that file.

In that same location, make a new class called MessagingController. Delete the auto-generated code and add the below code, leaving the package line at the top:

@RestController
public class MessagingController {
   private final Set<String> repeatCallers = Collections.synchronizedSet(new HashSet<>());

   @PostMapping(path="/messages", produces = "text/plain")
   public String service(@RequestParam("From") String fromNumber,
                         @RequestParam("Body") String symbol) {

      if (repeatCallers.add(fromNumber)){
           return "Welcome to stonksbot \uD83D\uDCC8. Text me a valid ticker symbol and I'll give you quote data.";
       }
       return "We've heard from you.";
   }
}

Start the server, ngrok, and send a message

In a terminal window, navigate to the root file of your application (probably something like whatsappbot). Run the following command to start the server:

./mvnw spring-boot:run

At this point, if you are missing any of the required dependencies then it will not compile or run. If you used the config link, it should have included Spring Web on setup. Once it is running, you can open up an ngrok tunnel to connect to localhost:8080, the default port for this application. If you changed the port for some reason then you will need to accommodate for this when running ngrok.

In a new terminal window, navigate to where ngrok is installed and run the below command:

ngrok http 8080 

When it’s running, it should look like this:

the ngrok window running on port 8080

Copy the https:// address and add /messages to the url since that is the name of the url path that you defined in the controller.

Log in to your Twilio account  and navigate to Programmable Messaging > Setting > WhatsApp Sandbox Settings. If it’s your first time setting up the Twilio Sandbox for WhatsApp, follow the prompts. It will ask you to message a number with a 415 area code with a unique text code; for example, mine is join shop-birds.

Once that is working, you can paste the secure ngrok link into the webhook window for “When a message comes in.”

image of the sandbox configuration where you must paste your ngrok link for "When a message comes in"

Now when you message anything to your WhatsApp sandbox you should see the below. If it doesn’t work, check that you still have your server running, that ngrok is configured correctly and running, and that you have the correct endpoint in the sandbox (check for https and that you have the /messages slug).

image of an iPhone with the sandbox confirmation message, and the first message from the stocks bot

Create a service for the Finnhub API and add env variable

Make a new class in the same location called FinnhubService. Delete the auto-generated code and paste the below code in

public class FinnhubService {

   public static final String FINNHUB_TOKEN = System.getenv("FINNHUB_API_KEY");

   private static final ObjectMapper MAPPER = new ObjectMapper();

   public static Optional<Price> getStockDetails(String symbol) {

       try {

           String stockUrlQuery = URLEncoder.encode(symbol.toUpperCase(), StandardCharsets.UTF_8);
           String tokenUrlQuery = URLEncoder.encode(FINNHUB_TOKEN, StandardCharsets.UTF_8);

           URL url = new URL("https://finnhub.io/api/v1/quote?symbol=" + stockUrlQuery + "&token=" + tokenUrlQuery);
           HttpURLConnection connection = (HttpURLConnection) url.openConnection();
           connection.setRequestProperty("accept", "application/json");
           InputStream responseStream = connection.getInputStream();
           Price price = MAPPER.readValue(responseStream, Price.class);

           if ("0".equals(price.open)){
               return Optional.empty();

           } else {
               return Optional.of(price);
           }

       } catch (IOException e){
           e.printStackTrace();
           return Optional.empty();
       }

   }
}

This code is making a GET request to the Finnhub API and then handling the JSON response. It then maps the response to an object called Price (you will get to the object mapper in the next step). Finally, it sends that object back to the Controller so it can be formatted nicely in a message.

If you want to try out making the request before running it in your code, you can use a tool like Postman. There is also a separate Finnhub API sandbox key that you can use if you want to test out any of the premium data (they are marked as premium in the documentation) or you have concerns about hitting rate limits (60/1 minute, so probably nothing to worry about).

There are a variety of ways to make HTTP requests in Java (check out Matthew’s excellent post on 5 ways to make HTTP requests with Java here). You will use a classic method in this example that is already included in your Java developer kit, primarily so you won’t need to add any extra libraries.

Notice that the URL has the parameter symbol, which is the stock market ticker symbol that you will get from the body of the user’s message. You pass it in as a string.

It is also crucial that you cast the symbol to all uppercase letters in the event that the user texted in any lowercase letters; the API call will return null if the symbol is not entirely uppercase.

You also must set the token parameter here as the Finnhub API Key.

Remember at the beginning, you needed a Finnhub API key as a requirement? This is where that comes in. You will copy the API key from your Finnhub dashboard and set it as an environment variable.

what the API key should look like when you log in to Finnhub to get it, which is a white modal with a green button for api documentation and a black regenerate button

Setting environment variables is another task that has different ways of going about it. As long as it is hidden, you don’t commit it publicly anywhere, and you can still access it, any way is fine. You can go this route if you’d like, and then the code that is included above should work properly using System.getenv(). Replace the string of Xs with your own API key.

echo "export finnhubToken='XXXXXXXXXXXX'" > twilio.env
source ./twilio.env

Now that the finnhubToken string is set, it will pull the variable from your system. You can run the below command to make sure your .env is ignored if you decide to commit this code anywhere.

echo "twilio.env" >> .gitignore

Map the Price object

Per the Finnhub Stock Quote API documentation, you can see that the response comes back with the attributes as single letters. When you do the mapping, it can be helpful to give the response more descriptive names so you can make sure that when you format the message the right attributes are set to the right descriptions.

You will be able to send the user information like the stock’s open price of the day, high price of the day, low price of the day, current price, and previous close price.

public class Price {
    public final String open;
    public final String high;
    public final String low;
    public final String current;
    public final String close;
    public final String time;

    public Price(@JsonProperty("o") String open,
                 @JsonProperty("h") String high,
                 @JsonProperty("l") String low,
                 @JsonProperty("c") String current,
                 @JsonProperty("pc") String close,
                 @JsonProperty("t") String time) {
        this.open = open;
        this.high = high;
        this.low = low;
        this.current = current;
        this.close = close;
        this.time = time;
    }
}

In the previous code, you already returned the Price object from the service. Now you need to call the service in MessagingController.

Format the message nicely in the Controller

In MessagingController, below the if statement that checks for repeat callers add in the below code (see the full code on github). This will take the symbol that someone has texted in, and will use the messageBuilder to create a readable message to send back to the person who has messaged in.

   Optional<Price> price = FinnhubService.getStockDetails(symbol);

   if (price.isPresent()){
       return messageBuilder(price.get(), symbol);
   } else {
       return "Sorry. We couldn't find any info on that one. Try another.";
   }
}


private String messageBuilder(Price price, String symbol) {
   return String.format("Here are the most up-to-date %s prices:\n" +
                   "\uD83C\uDF05 Open price of the day: $%s\n" +
                   "\uD83D\uDCC8 High price of the day: $%s\n" +
                   "\uD83D\uDCC9 Low price of the day: $%s\n" +
                   "\uD83D\uDD14 Current price: $%s\n" +
                   "\uD83D\uDCC6 Previous close price: $%s\n" +
                   "\uD83E\uDD1E Send another symbol.",
           symbol.toUpperCase(), price.open, price.high, price.low, price.current, price.close);
}

Now you can see if it’s working.

Test it out

Re-run the server and send a WhatsApp message with a valid stock symbol to the bot. You should get the below:

An image of the bot and its response after it has been sent the TWLO ticker symbol

If it doesn’t work, check out the errors in the stack trace. The most common pitfalls are incorrectly set environment variables and missing variables that may have not been added in the steps.

Finance is fun. Java is fun.

Hopefully you enjoyed this post and are already thinking of additional data to add to your bot. You may also want to explore this post on how to check Etherum gas prices using Twilio Serverless. And if TypeScript is more your thing, you can build it with that instead.

If you ran into any bugs along the way, not to worry. You probably didn’t lose $460 million over it. If you do decide to make your own bot I’d love to hear about it. Email me lmoy[at]twilio.com to let me know, and send me a tweet to tell me about stock advice your build.

Liz Moy is a Developer Evangelist on the Enterprise Evangelism team. She loves to talk to developers and does so frequently on Build and Deploy with Liz Moy, a podcast powered by Twilio. You can find her at lmoy [at] twilio.com or on Twitter @ecmoy.