Salesforce Developers Blog

Integrate Einstein Bots into Any Channel Using the New SDK and Framework

Avatar for Rajasekar ElangoRajasekar Elango
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.
Integrate Einstein Bots into Any Channel Using the New SDK and Framework
May 25, 2022
Listen to this article
0:00 / 0:00

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.

1File f = new File(privateKeyFile);
2DataInputStream dis = new DataInputStream(new FileInputStream(f));
3byte[] keyBytes = new byte[(int) f.length()];
4dis.readFully(keyBytes);
5dis.close();
6
7PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
8KeyFactory kf = KeyFactory.getInstance("RSA");
9PrivateKey privateKey = kf.generatePrivate(spec);
10
11Map<String, Object> headers = new HashMap<String, Object>();
12headers.put("alg", "RS256");
13Algorithm algorithm = Algorithm.RSA256(null, (RSAPrivateKey) privateKey);

Then, create the JWT with appropriate values.

1Instant now = Instant.now();
2String jwt = JWT.create()
3    .withHeader(headers)
4    .withAudience(loginEndpoint)
5    .withExpiresAt(Date.from(now.plus(jwtExpiryMinutes, ChronoUnit.MINUTES)))
6    .withIssuer(connectedAppId)
7    .withSubject(userId)
8    .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.

1// Create Http Post Form data.
2MultiValueMap<String, String> formData= new LinkedMultiValueMap<>();
3formData.add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
4formData.add("assertion", jwt);
5
6// Create Http Headers.
7HttpHeaders httpHeaders = new HttpHeaders();
8httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
9
10// Create Http Request.
11HttpEntity<Map> oAuthHttpRequest = new HttpEntity<>(formData, httpHeaders);
12
13// Post Request to OAuth Endpoint
14ResponseEntity<String> response = restTemplate
15    .postForEntity(OAUTH_URL, oAuthHttpRequest, String.class);

Then, parse the response to get the access_token:

1ObjectNode node = new ObjectMapper().readValue(response.getBody(), ObjectNode.class);
2String 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.

1// Create Http Headers
2HttpHeaders requestHeaders = new HttpHeaders();
3requestHeaders.setContentType(MediaType.APPLICATION_JSON);
4requestHeaders.setBearerAuth(token);
5requestHeaders.add("X-Org-Id", orgId);
6
7String message = "Hello";
8
9// Create Request Body in TextMessage format defined in Schema.
10
11// We are using string concatenation here just for sake of simplicity.
12// You will likely create a DTO classes that can help
13// with JSON serialization/deserialization in real production code.
14String requestBody =
15    "{\n"
16        + "  \"externalSessionKey\": \"" + UUID.randomUUID().toString + "\",\n"
17        + "  \"message\": {\n"
18        + "    \"text\": \"" + message + "\"\n"
19        + "  },\n"
20        + "  \"forceConfig\": {\n"
21        + "    \"endpoint\": \"" + forceConfigEndPoint + "\"\n"
22        + "  }\n"
23    + "}";
24
25// Create HTTP Request
26HttpEntity<String> httpRequest = new HttpEntity<>(requestBody, requestHeaders);
27
28// Create URL with URI format v5.0.0/bots/{botId}/sessions
29String url = RUNTIME_BASE_URL + "/v5.0.0/bots/" + botId + "/sessions";
30
31// Send Start Chat Session Request
32ResponseEntity<String> startSessionResponse = restTemplate
33    .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:

1{
2  "sessionId" : "9168d0c9-bbe2-4be1-a1a1-8a3902921e87",
3  "botVersion" : "0X9SB00000007cb0AA",
4  "messages" : [
5    {
6      "id" : "c0a95f75-06b2-4269-b88d-c119e038781f",
7      "schedule" : {
8        "responseDelayMilliseconds" : 1200
9      },
10      "type" : "text",
11      "text" : "Hi,"
12    },
13    {
14      "id" : "d45cdaa8-b278-42d1-b954-53f4db9ea204",
15      "schedule" : {
16        "responseDelayMilliseconds" : 1200
17      },
18      "type" : "text",
19      "text" : "I’m a demo bot for the user guide."
20    },
21    {
22      "id" : "4c484926-c6c5-4944-b650-cce011b0f3b6",
23      "schedule" : {
24        "responseDelayMilliseconds" : 1200
25      },
26      "type" : "text",
27      "text" : "Choose one of the options below"
28    },
29    {
30      "id" : "d05f2b3a-2453-4acb-9c33-43774487c76c",
31      "schedule" : {
32        "responseDelayMilliseconds" : 1200
33      },
34      "type" : "choices",
35      "widget" : "menu",
36      "choices" : [
37        {
38          "label" : "Order Status",
39          "alias" : "1",
40          "id" : "2319a485-5c5c-4c27-8239-abb8f7366ff6"
41        },
42        {
43          "label" : "Frequently Asked Questions",
44          "alias" : "2",
45          "id" : "664d72d9-2a54-477b-9f9b-da2d01c58552"
46        },
47        {
48          "label" : "Transfer To Agent",
49          "alias" : "3",
50          "id" : "dccd1287-8fc0-426f-ae0b-0a8b6a41b011"
51        },
52        {
53          "label" : "End Chat",
54          "alias" : "4",
55          "id" : "c359eaea-f981-49ba-a424-18b8b7572d20"
56        }
57      ]
58    }
59  ],
60  "processedSequenceIds" : [
61    0
62  ],
63  "_links" : {
64    "session" : {
65      "href" : "https://runtime-api-na-west.stg.chatbots.sfdc.sh/v5.0.0/sessions/9168d0c9-bbe2-4be1-a1a1-8a3902921e87"
66    },
67    "self" : {
68      "href" : "https://runtime-api-na-west.stg.chatbots.sfdc.sh/v5.0.0/bots/0XxSB00000007UX0AY/sessions"
69    },
70    "messages" : {
71      "href" : "https://runtime-api-na-west.stg.chatbots.sfdc.sh/v5.0.0/sessions/9168d0c9-bbe2-4be1-a1a1-8a3902921e87/messages"
72    }
73  }
74}

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

1// Print Response Body that has response from Chatbot.
2System.out.println("Bot Start Session Response : " 
3    + startSessionResponse.getBody());
4
5// Get SessionId from Response to send message to existing Chat Session.
6JsonNode responseNode = mapper
7    .readValue(startSessionResponse.getBody(), JsonNode.class);
8    
9String 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.

1<dependency>
2  <groupId>com.salesforce.einsteinbot</groupId>
3  <artifactId>einstein-bot-sdk-java</artifactId>
4  <version>${einstein-bot-sdk-java-version}</version>
5</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.

1//Create JwtBearer Auth Mechanism.
2AuthMechanism oAuth = JwtBearerOAuth.with()
3    .privateKeyFilePath(privateKeyFilePath)
4    .loginEndpoint(loginEndpoint)
5    .connectedAppId(connectedAppId)
6    .connectedAppSecret(secret)
7    .userId(userId)
8    .build();
9
10//Create Basic Chatbot Client
11BasicChatbotClient client = ChatbotClients.basic()
12    .basePath(basePath)
13    .authMechanism(oAuth)
14    .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.

1//Create Request Config
2RequestConfig config = RequestConfig.with()
3    .botId(botId)
4    .orgId(orgId)
5    .forceConfigEndpoint(forceConfigEndPoint)
6    .build();

Then, create a BotSendMessageRequest with TextMessage.

1// We can use statically typed Java classes for Request Body.
2AnyRequestMessage message = new TextMessage()
3    .text("Hello")
4    .type(TextMessage.TypeEnum.TEXT)
5    .sequenceId(System.currentTimeMillis());
6
7BotSendMessageRequest botSendInitMessageRequest = BotRequest
8    .withMessage(message)
9    .build();

Use the startChatSession method to start a session.

1ExternalSessionId externalSessionKey = 
2    new ExternalSessionId(UUID.randomUUID().toString());
3
4BotResponse resp = client
5    .startChatSession(config, externalSessionKey, botSendInitMessageRequest);
6    
7// Get SessionId from Response.    
8String 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.

1List<AnyResponseMessage> messages = resp.getResponseEnvelope().getMessages();
2StringBuilder sb = new StringBuilder();
3for(AnyResponseMessage message : messages){
4    if (message instanceof TextResponseMessage){
5    sb.append(((TextResponseMessage) message).getText())
6        .append("\n");
7    }else if (message instanceof ChoicesResponseMessage){
8    List<ChoicesResponseMessageChoices> choices = ((ChoicesResponseMessage) message)
9        .getChoices();
10    for (ChoicesResponseMessageChoices choice : choices){
11        sb.append(choice.getAlias())
12            .append(".")
13            .append(choice.getLabel())
14            .append("\n");
15    }
16    }
17    //Similarly handle other Response Message Types.
18}
19String responseMessageAsText = sb.toString();
20System.out.println(responseMessageAsText);

The output will look like this:

1Hi,
2I’m a demo bot for the user guide.
3Choose one of the options below
41.Order Status
52.Frequently Asked Questions
63.Transfer To Agent
74.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.

1// SDK also provides utility methods to create a text message.
2// Let's say, we want to respond to the menu choice with "Order Status".
3AnyRequestMessage userInputMessage = RequestFactory
4    .buildTextMessage("Order Status");
5    
6// Build Bot Send Message Request with user's response message.     
7BotSendMessageRequest botSendMessageRequest =  BotRequest
8    .withMessage(userInputMessage)
9    .build();
10
11//Create RuntimeSessionId with sessionId you got from start chat session Response.
12 RuntimeSessionId runtimeSessionId = new RuntimeSessionId(sessionId);
13    
14// Send a message to existing Session with sessionId
15BotResponse textMsgResponse = client
16    .sendMessage(config, runtimeSessionId, botSendMessageRequest);
17
18System.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.

1// Build Bot End Session Message Request
2BotEndSessionRequest botEndSessionRequest = BotRequest
3    .withEndSession(EndSessionReason.USERREQUEST).build();
4    
5//Create RuntimeSessionId with sessionId you got from start chat session Response.
6RuntimeSessionId runtimeSessionId = new RuntimeSessionId(sessionId);
7
8// Send Request to End Chat session
9BotResponse endSessionResponse = client
10    .endChatSession(config, runtimeSessionId, botEndSessionRequest);
11
12System.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.

An image summarizing Einstein Bots tools and their benefits

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.