Einstein Bots is a conversational chatbot solution that is powered by Salesforce’s Einstein AI Platform. Its job is to interact with customers and provide them with the information they need quickly without human intervention. It can also handles simple, repetitive tasks, freeing up agents to manage more complex cases.

In Spring ‘22, we released the Einstein Bots Platform API (Beta) to help customers leverage power of Einstein Bots on any digital channel. In Summer ’22, we have made the Java SDK and open-source Channel Connector available to simplify the bot developer experience. This gives you the tools you need to easily integrate Einstein Bots into any of your conversational channels on top of the existing digital engagement channels that are supported by Service Cloud.

In this post, we’ll look at how you can use the Einstein Bots Platform API, and we’ll also cover how to use the SDK and its benefits. Check out this previous blog post to get even more familiar with the Einstein Bots Platform API.

Using the Einstein Bots Platform API

The Einstein Bots Platform API is a REST API, and you can use it without the SDK. The Einstein Bots Platform API Client Guide provides instructions on how to integrate Einstein Bots with your channel using CURL or Postman. Let’s look at the actual Java code required to work with the Einstein Bots Platform API.

1. Create the JSON Web Token (JWT)

Einstein Bots require requests to be authenticated using OAuth. Any OAuth flows can be used to get the access token. Since this is a service-to-service integration, we will use the JWT bearer OAuth flow to mint the JWT and get the OAuth access token. Use your private key that you created in your connected app setup to create the algorithm for signing the JWT.

File f = new File(privateKeyFile);
DataInputStream dis = new DataInputStream(new FileInputStream(f));
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);

Map<String, Object> headers = new HashMap<String, Object>();
headers.put("alg", "RS256");
Algorithm algorithm = Algorithm.RSA256(null, (RSAPrivateKey) privateKey);

Then, create the JWT with appropriate values.

Instant now = Instant.now();
String jwt = JWT.create()
    .withHeader(headers)
    .withAudience(loginEndpoint)
    .withExpiresAt(Date.from(now.plus(jwtExpiryMinutes, ChronoUnit.MINUTES)))
    .withIssuer(connectedAppId)
    .withSubject(userId)
    .sign(algorithm);

2. Get the OAuth access token

Send an HTTP post request to the https://login.salesforce.com/services/oauth2/token endpoint with jwt in the request body and using the appropriate HTTP headers.

// Create Http Post Form data.
MultiValueMap<String, String> formData= new LinkedMultiValueMap<>();
formData.add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
formData.add("assertion", jwt);

// Create Http Headers.
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

// Create Http Request.
HttpEntity<Map> oAuthHttpRequest = new HttpEntity<>(formData, httpHeaders);

// Post Request to OAuth Endpoint
ResponseEntity<String> response = restTemplate
    .postForEntity(OAUTH_URL, oAuthHttpRequest, String.class);

Then, parse the response to get the access_token:

ObjectNode node = new ObjectMapper().readValue(response.getBody(), ObjectNode.class);
String token = node.get("access_token").asText();

Now, we have the token required for authentication and are ready to make requests to the Einstein Bots Platform API.

3. Send a start chat session request

Send an HTTP post request to the https://<RUNTIME_BASE_URL>/v5.0.0/bots/{botId}/sessions endpoint with the authentication token and orgId in the HTTP headers. The RUNTIME_BASE_URL can be obtained from the Bot Overview page documented in the client guide.

// Create Http Headers
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
requestHeaders.setBearerAuth(token);
requestHeaders.add("X-Org-Id", orgId);

String message = "Hello";

// Create Request Body in TextMessage format defined in Schema.

// We are using string concatenation here just for sake of simplicity.
// You will likely create a DTO classes that can help
// with JSON serialization/deserialization in real production code.
String requestBody =
    "{\n"
        + "  \"externalSessionKey\": \"" + UUID.randomUUID().toString + "\",\n"
        + "  \"message\": {\n"
        + "    \"text\": \"" + message + "\"\n"
        + "  },\n"
        + "  \"forceConfig\": {\n"
        + "    \"endpoint\": \"" + forceConfigEndPoint + "\"\n"
        + "  }\n"
    + "}";

// Create HTTP Request
HttpEntity<String> httpRequest = new HttpEntity<>(requestBody, requestHeaders);

// Create URL with URI format v5.0.0/bots/{botId}/sessions
String url = RUNTIME_BASE_URL + "/v5.0.0/bots/" + botId + "/sessions";

// Send Start Chat Session Request
ResponseEntity<String> startSessionResponse = restTemplate
    .postForEntity(url, httpRequest , String.class);

You can parse the response to show the bot’s message to the user depending on the channel. For this example, we will just output to console which will look like this:

{
  "sessionId" : "9168d0c9-bbe2-4be1-a1a1-8a3902921e87",
  "botVersion" : "0X9SB00000007cb0AA",
  "messages" : [
    {
      "id" : "c0a95f75-06b2-4269-b88d-c119e038781f",
      "schedule" : {
        "responseDelayMilliseconds" : 1200
      },
      "type" : "text",
      "text" : "Hi,"
    },
    {
      "id" : "d45cdaa8-b278-42d1-b954-53f4db9ea204",
      "schedule" : {
        "responseDelayMilliseconds" : 1200
      },
      "type" : "text",
      "text" : "I’m a demo bot for the user guide."
    },
    {
      "id" : "4c484926-c6c5-4944-b650-cce011b0f3b6",
      "schedule" : {
        "responseDelayMilliseconds" : 1200
      },
      "type" : "text",
      "text" : "Choose one of the options below"
    },
    {
      "id" : "d05f2b3a-2453-4acb-9c33-43774487c76c",
      "schedule" : {
        "responseDelayMilliseconds" : 1200
      },
      "type" : "choices",
      "widget" : "menu",
      "choices" : [
        {
          "label" : "Order Status",
          "alias" : "1",
          "id" : "2319a485-5c5c-4c27-8239-abb8f7366ff6"
        },
        {
          "label" : "Frequently Asked Questions",
          "alias" : "2",
          "id" : "664d72d9-2a54-477b-9f9b-da2d01c58552"
        },
        {
          "label" : "Transfer To Agent",
          "alias" : "3",
          "id" : "dccd1287-8fc0-426f-ae0b-0a8b6a41b011"
        },
        {
          "label" : "End Chat",
          "alias" : "4",
          "id" : "c359eaea-f981-49ba-a424-18b8b7572d20"
        }
      ]
    }
  ],
  "processedSequenceIds" : [
    0
  ],
  "_links" : {
    "session" : {
      "href" : "https://runtime-api-na-west.stg.chatbots.sfdc.sh/v5.0.0/sessions/9168d0c9-bbe2-4be1-a1a1-8a3902921e87"
    },
    "self" : {
      "href" : "https://runtime-api-na-west.stg.chatbots.sfdc.sh/v5.0.0/bots/0XxSB00000007UX0AY/sessions"
    },
    "messages" : {
      "href" : "https://runtime-api-na-west.stg.chatbots.sfdc.sh/v5.0.0/sessions/9168d0c9-bbe2-4be1-a1a1-8a3902921e87/messages"
    }
  }
}

Next, we will need to extract the sessionId from the response to continue sending messages to the same session.

// Print Response Body that has response from Chatbot.
System.out.println("Bot Start Session Response : " 
    + startSessionResponse.getBody());

// Get SessionId from Response to send message to existing Chat Session.
JsonNode responseNode = mapper
    .readValue(startSessionResponse.getBody(), JsonNode.class);
    
String sessionId = responseNode.get("sessionId").asText();

As you can see, there is a lot of boilerplate code required to use Einstein Bots Platform API directly. To simplify the integration and reduce much of the boilerplate code, we created an Einstein Bots SDK for Java.

Using the Java SDK to simplify an Einstein Bots integration

The SDK is a wrapper around the Einstein Bots Platform API that simplifies the integration by providing added features, such as authorization support and session management. Let’s look at some code for implementing the same example using the Einstein Bots SDK.

1. Add a POM dependency

Find the latest einstein-bot-sdk-java version from Maven Central and add this dependency to your pom.xml.

<dependency>
  <groupId>com.salesforce.einsteinbot</groupId>
  <artifactId>einstein-bot-sdk-java</artifactId>
  <version>${einstein-bot-sdk-java-version}</version>
</dependency>

2. Create a chatbot client

The chatbot client provides JwtBearerFlow for OAuth, so create AuthMechanism with appropriate parameters. Then, create BasicChatbotClient with the auth mechanism and basePath of the Bot runtime URL.

//Create JwtBearer Auth Mechanism.
AuthMechanism oAuth = JwtBearerOAuth.with()
    .privateKeyFilePath(privateKeyFilePath)
    .loginEndpoint(loginEndpoint)
    .connectedAppId(connectedAppId)
    .connectedAppSecret(secret)
    .userId(userId)
    .build();

//Create Basic Chatbot Client
BasicChatbotClient client = ChatbotClients.basic()
    .basePath(basePath)
    .authMechanism(oAuth)
    .build();

3. Send start chat session request

First, create a RequestConfig with your botId, orgId, and forceConfigEndPoint. You can refer to the client guide to find these values. Typically, you want to create a config once per bot in your org and reuse it for every request.

//Create Request Config
RequestConfig config = RequestConfig.with()
    .botId(botId)
    .orgId(orgId)
    .forceConfigEndpoint(forceConfigEndPoint)
    .build();

Then, create a BotSendMessageRequest with TextMessage.

// We can use statically typed Java classes for Request Body.
AnyRequestMessage message = new TextMessage()
    .text("Hello")
    .type(TextMessage.TypeEnum.TEXT)
    .sequenceId(System.currentTimeMillis());

BotSendMessageRequest botSendInitMessageRequest = BotRequest
    .withMessage(message)
    .build();

Use the startChatSession method to start a session.

ExternalSessionId externalSessionKey = 
    new ExternalSessionId(UUID.randomUUID().toString());

BotResponse resp = client
    .startChatSession(config, externalSessionKey, botSendInitMessageRequest);
    
// Get SessionId from Response.    
String sessionId = resp.getResponseEnvelope().getSessionId();

Parse the response envelope and display the message to the user depending on the channel. The SDK will automatically deserialize the JSON to a Java model to make it easier. The code below shows how to parse the response as text for TextResponseMessage and ChoiceResponseMessage types. For all supported types and code, refer to the schema.

List<AnyResponseMessage> messages = resp.getResponseEnvelope().getMessages();
StringBuilder sb = new StringBuilder();
for(AnyResponseMessage message : messages){
    if (message instanceof TextResponseMessage){
    sb.append(((TextResponseMessage) message).getText())
        .append("\n");
    }else if (message instanceof ChoicesResponseMessage){
    List<ChoicesResponseMessageChoices> choices = ((ChoicesResponseMessage) message)
        .getChoices();
    for (ChoicesResponseMessageChoices choice : choices){
        sb.append(choice.getAlias())
            .append(".")
            .append(choice.getLabel())
            .append("\n");
    }
    }
    //Similarly handle other Response Message Types.
}
String responseMessageAsText = sb.toString();
System.out.println(responseMessageAsText);

The output will look like this:

Hi,
I’m a demo bot for the user guide.
Choose one of the options below
1.Order Status
2.Frequently Asked Questions
3.Transfer To Agent
4.End Chat

We have different Einstein Bots Platform API endpoints for continuing an existing session and ending a chat session. For completeness, let’s look at code examples for them.

4. Send a message to an existing chat session

The example code shows how to use the sendMessage method to send a message to an existing open chat session.

// SDK also provides utility methods to create a text message.
// Let's say, we want to respond to the menu choice with "Order Status".
AnyRequestMessage userInputMessage = RequestFactory
    .buildTextMessage("Order Status");
    
// Build Bot Send Message Request with user's response message.     
BotSendMessageRequest botSendMessageRequest =  BotRequest
    .withMessage(userInputMessage)
    .build();

//Create RuntimeSessionId with sessionId you got from start chat session Response.
 RuntimeSessionId runtimeSessionId = new RuntimeSessionId(sessionId);
    
// Send a message to existing Session with sessionId
BotResponse textMsgResponse = client
    .sendMessage(config, runtimeSessionId, botSendMessageRequest);

System.out.println("Text Message Response :" + textMsgResponse);

We used TextMessage in this example, but you can also send other types (e.g., ChoiceMessage) supported by the Einstein Bot Runtime Open API Schema.

5. End a chat session

Finally, here is example code for ending the session using the endChatSession method.

// Build Bot End Session Message Request
BotEndSessionRequest botEndSessionRequest = BotRequest
    .withEndSession(EndSessionReason.USERREQUEST).build();
    
//Create RuntimeSessionId with sessionId you got from start chat session Response.
RuntimeSessionId runtimeSessionId = new RuntimeSessionId(sessionId);

// Send Request to End Chat session
BotResponse endSessionResponse = client
    .endChatSession(config, runtimeSessionId, botEndSessionRequest);

System.out.println("End Session Response :" + endSessionResponse);

Benefits of using the Einstein Bots SDK

The Einstein Bots SDK provides lots of great features that can save developers time.

  • It abstracts out the details of loading private key and minting JWT.
  • It abstracts out token exchange to get OAuth access token.
  • It provides model classes with strict type checking.
    • You don’t have to use convoluted JSON string serializing/deserializing of request/response bodies.
    • We used TextMessage in this example. Similarly, model classes are available for all schema objects defined in Bot Runtime Open API Schema.
  • The code is more readable due to the dsl-like methods.
  • If you use BasicChatbotClient demonstrated in the blog, you will need to keep track of sessions and call startChatSession or sendMessage method appropriately. Instead, use SessionManagedChatbotClient, which eliminates the startChatSession method. It will automatically create a new session based on the user-provided ExternalSessionId. We will publish a follow-up blog post on using SessionManagedChatbotClient.

And there’s more: the Channel Connector framework

In addition to the Einstein Bots SDK, we also released the Einstein Bots Channel Connector framework to simplify building a channel connector service using Spring Boot. It auto-configures Spring beans for foundational services, such as caching, authentication, and metrics, and makes it ready to use out of the box.

The Channel Connector framework includes a working example application and a maven archetype for creating a new bot channel connector application.

The image below summarizes the tools that we are releasing and the benefits that they provide.

Where to go from here?

About the author

Rajasekar Elango is a Principal Software Engineer at Salesforce working on the Einstein Bots Platform. You can follow him on LinkedIn or Twitter.

Get the latest Salesforce Developer blog posts and podcast episodes via Slack or RSS.

Add to Slack Subscribe to RSS