chatbot custom api

요청

Method Request URI
POST Chatbot 빌더에서 생성한 도메인에 연동된 API Gateway의 InvokeURL로 호출합니다.
각 도메인마다 고유의 호출 URL이 생성됩니다.
예시) https://mmrm1gp7p7.apigw.ntruss.com/send/beta

Custom Chatbot으로 연동하려면 생성한 Stage의 Invoke URL과 Secret Key를 복사하여 보관합니다.

요청 헤더

헤더명 설명
X-NCP-CHATBOT_SIGNATURE 도메인에서 API Gateway 연동시 생성한 X-NCP-CHATBOT_SIGNATURE:{Client ID}
Content-Type application/json;UTF-8

Signature 생성

Chatbot Custom API에서 Signature 생성 방법을 자세하게 설명하기 위해서 Server API의 getRegionList 액션을 예시로 들겠습니다.

  1. HTTP 호출 형태로 요청하고자 하는 액션을 문자열로 만듭니다. 이때 호출하는 API 주소는 제외가 되어야 합니다. (https://ncloud.apigw.ntruss.com)
    • GET /server/v2/getRegionList {x-ncp-apigw-timestamp} {x-ncp-iam-access-key}
  2. AccessKey에 대응하는 SecretKey를 이용하여 HmacSHA256알고리즘으로 암호화를 합니다.
  3. 한글이 들어갈 경우 글이 깨지는 경우가 발생하기 때문에 base64로 변환이 필요합니다.

언어별 Signature 생성 예제코드

  • js code sample

    const HmacSHA256 = require('crypto-js/hmac-sha256');
    const EncBase64 = require('crypto-js/enc-base64');
    signatureHeader = HmacSHA256(requestBodyString, secretKey).toString(EncBase64);
    

  • java code sample

    byte[] secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyBytes, "HmacSHA256");
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(secretKeySpec);
    byte[] signature = mac.doFinal(body.getBytes(StandardCharsets.UTF_8));
    String signatureHeader = Base64.getEncoder().encodeToString(signature);
    

요청 바디

Open

  • 웰컴 메시지에 응답
  • trigger when open messenger, will response welcome message if have set in “Messenger Connection” -> “Custom”
구분 Json Model - Example
Open {
“version”: “v2”,
“userId”: “U47b00b58c90f8e47428af8b7bddcda3d”,
“userIp”: “8.8.8.8”,
“timestamp”: 12345678,
“bubbles”: [ {
“type”: “text”,
“data” : { “description” : “postback text of welcome action” } } ],
“event”: “open”
}

Open 요청 필드 상세 설명

필드 이름 데이터 타입 필수여부 설명
version string false version은 ‘v2’가 기본이며, 값이 입력되지 않은 경우 ‘v1’으로 설정됩니다.
userId string true 봇과 채팅하는 사용자의 고유 ID로 최대 256자를 넘지 않아야 합니다. 사용자마다 고유한 userId를 설정하십시오.
userIp string false 사용자의 Ip Adress 이며, 필수는 아닙니다.
timestamp long true 타임스탬프 값입니다.
January 1, 1970, 00:00:00 GMT
bubbles array true empty array “[]” or only one Text component caused by welcome action
event string true 이벤트 value 값을 “open”으로 설정합니다.

send

Chatbot으로 이용자의 질문을 전달합니다.

send 호출시, userid 값은 유일한 키값이면 아무거나 써도 됩니다. (예 회원번호 등)

  • userid : 유일한 키값 , description : 사용자 질의
구분 Json Model - Example
Open {
“version”: “v2”,
“userId”: “U47b00b58c90f8e47428af8b7bddcda3d”,
“userIp”: “8.8.8.8”,
“timestamp”: 12345678,
“bubbles”: [ {
“type”: “text”,
“data” : { “description” : “text content which is user input” } } ],
“event”: “send”
}

send 요청 필드 상세 설명

필드 이름 데이터 타입 필수여부 설명
version string false version은 ‘v2’가 기본이며, 값이 입력되지 않은 경우 ‘v1’으로 설정됩니다.
userId string true 봇과 채팅하는 사용자의 고유 ID로 최대 256자를 넘지 않아야 합니다. 사용자마다 고유한 userId를 설정하십시오.
userIp string false 사용자의 Ip Adress 이며, 필수는 아닙니다.
timestamp long true 타임스탬프 값입니다.
January 1, 1970, 00:00:00 GMT
bubbles array[Text] true 하나의 Text Component 만 지원합니다.
하나 이상의 Text Component가 있을 경우, 마지막 Component를 사용자 요청을 사용합니다.
event string true 이벤트 value 값을 “send”으로 설정합니다.

getPersistentMenu

고정 메뉴 목록 가져오기

if need show persistent menu but local cache not exists, could request PersistentMenu, will response persistentMenu field if fixed menu have set in messengers custom tab.

고정 메뉴 표시가 필요하지만 로컬 캐시가 존재하지 않으면 PersistentMenu를 요청할 수 있으며, 고정된 경우 persistMenu 필드에 응답합니다. 메뉴가 메신저 사용자 정의 탭에서 설정되었습니다.

구분 Json Model - Example
getPersistentMenu {
“version”: “v2”,
“userId”: “U47b00b58c90f8e47428af8b7bddcda3d”,
“userIp”: “8.8.8.8”,
“timestamp”: 12345678,
“bubbles”: [],
“event”: “getPersistentMenu”
}

getPersistentMenu요청 필드 상세 설명

필드 이름 데이터 타입 필수여부 설명
version string false version은 ‘v2’가 기본이며, 값이 입력되지 않은 경우 ‘v1’으로 설정됩니다.
userId string true 봇과 채팅하는 사용자의 고유 ID로 최대 256자를 넘지 않아야 합니다. 사용자마다 고유한 userId를 설정하십시오.
userIp string false 사용자의 Ip Adress 이며, 필수는 아닙니다.
timestamp long true 타임스탬프 값입니다.
January 1, 1970, 00:00:00 GMT
bubbles array true 빈 Array 값으로 설정합니다. “[]”
event string true 이벤트 value 값을 “getPersistentMenu”으로 설정합니다.

응답

응답 결과 Status (성공 및 실패)

Success

  • 챗봇 질의에 성공한 경우, Http Status Code는 200을 리턴하며, 응답 필드는 다음과 같습니다.
구분 Json Model - Example
Success {
“version”: “v2”,
“userId”: “U47b00b58c90f8e47428af8b7bddcda3d”,
“sessionId”: “34a59946-5dcb-4b72-9b63-a773c659702e”,
“timestamp”: 12345678,
“bubbles”: [ // each component is a bubble ],
“quickButtons”: [ // some buttons ],
“scenario”: {
“name”: “analyzedScenarioName”,
“intent”: [ // some scenario intent ] },
“entities”: [ {
“word”: “userInputWord”,
“name”: “analyzedEntityName” } ],
“keywords”: [ {
“keyword”: “userInputKeyword”,
“group”: “analyzedKeywordGroupName”,
“type”: “analyzedKeywordType” } ],
“persistentMenu”: { // one template component },
“event”: “send”
}

응답 결과(성공) 필드 상세 설명

필드 이름 데이터 타입 필수여부 설명
version string false version은 ‘v2’가 기본이며, 값이 입력되지 않은 요청의 경우 ‘v1’으로 응답됩니다.
userId string true 요청 시 설정한 userId 값과 동일합니다.
sessionId string false 현재 session id 이며, chatbot에서 관리하는 값입니다.
timestamp long true 응답 타임스탬프 값입니다. (milliseconds , January 1, 1970, 00:00:00 GMT)
bubbles array[Component] false 응답 컴포넌트의 배열입니다. 각 컴포넌트는 챗봇의 응답 Bubble과 매치 됩니다.
quickButtons array[Component] false 챗봇 하단에 설정된 고정버튼(quickButton)의 정보입니다.
scenario jsonObject false 사용자의 질의에 일치된 시나리오 분석 결과입니다. 시나리오 이름 및 의도(대화유형) 정보를 제공합니다.
entities array[jsonObject] false 사용자의 질의에 일치된 엔티티(entity)분석 결과입니다.
keywords array[jsonObject] false 사용자 채팅에서 키워드와 일치하는 단어 키워드에는 “exactMatch”또는 “contain”, “exactMatch”의 두 가지 유형이 있습니다. “exactMatch”는 사용자 입력이 키워드와 완전히 일치 함을 의미하고 “contain”은 사용자 입력에 키워드가 포함됨을 의미합니다.
persistentMenu Template Component false persistent menu를 설정합니다.PersistentMenu
event string true 고정된 응답 “send”

new general

Error

Error

  • 챗봇 질의에 실패한 경우, Http Status Code는 500을 리턴하며, 응답 필드는 다음과 같습니다.

500 오류 외 자세한 오류 코드는 문서 하단에 자세히 설명합니다.

HttpStatusCode Description
500 Internal server error

Error Response Body:

{
    "code": "500",
    "message": "Internal server error",
    "timestamp": 12345678
}

Component

챗봇 답변의 응답 컴포넌트는 아래와 같은 json 구조를 따릅니다.

{
  "type": "...",
  "title": "optional, short bold text",
  "subTitle": "optional, short gray text",
  "data" : {
    ...
  }
}

가지 형태의 컴포넌트가 있습니다.

  • Basic Component
  • Composite Component
  • Flex Component

Basic Component

Basic 컴포넌트는 Text, Image, Button의 3가지의 기본 컴포넌트를 제공합니다.

  1. Text

챗봇의 답변할 Text 컴포넌트 형태이며, 타이틀, 서브 타이틀 그리고, 자세한 설명과 URL을 설정할 수 있습니다.

  • Text 컴포넌트 구조

new text

  • Text 컴포넌트 Json
구분 Json Model - Example
Text { “type”: “text”, “title”: “optional, short bold text”, “subTitle”: “optional, short gray text”,“data” : {“description” : “optional, a long text content”,“url” : “optional, a hyperlink at the bottom of description”,“urlAlias” :“optional, hyperlink show this alias”,“action”: {Action Data} }}
  • Text 컴포넌트 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true text
title string false short bold text
subTitle string false short gray text
data.description string false a long text content
data.url string false the hyperlink jump url
data.urlAlias string false the hyperlink show text
data.action Action false the action of click on text or title
  1. Image

Image를 포함한 답변 컴포넌트 형태이며, 이미지와 함께 타이틀, 서브 타이틀 그리고, 자세한 설명과 URL을 설정할 수 있습니다.

  • Image 컴포넌트 구조

new image

  • Image 컴포넌트 Json
구분 Json Model - Example
Image {
“type”: “image”,
“title”: “optional, short bold text”,
“subTitle”: “optional, short gray text”,
“data” : {
“imageUrl” : “https://ssl.pstatic.net/CloudFunctions.png",
“alt” : “optional, short hint show when hover on image”,
“imagePosition” : “top”,
“description” : “optional, details info of image”,
“url” : “optional, a hyperlink at the bottom of description”,
“urlAlias” : “optional, hyperlink show this alias”,
“action”: {Action Data}
}
  • Image 컴포넌트 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true image
title string false short bold text
subTitle string false short gray text
data.imageUrl string true image url, must be https url
data.alt string false short hint text show hover on image
data.imagePosition string false top / bottom / left / right, default is top
data.description string false details info of image
data.url string false the hyperlink jump url
data.urlAlias string false the hyperlink show text
data.action Action false the action of click on image or title
  1. Button

Image를 포함한 답변 컴포넌트 형태이며, 이미지와 함께 타이틀, 서브 타이틀 그리고, 자세한 설명과 URL을 설정할 수 있습니다.

  • Button 컴포넌트 구조

new button

  • Button 컴포넌트 Json
구분 Json Model - Example
Button (Basic Button) {
“type”: “button”,
“title”: “optional, text show on button”,
“subTitle”: “optional, short gray text”,
“data” : {
“type”: “basic”,
“iconUrl” : “https://ssl.pstatic.net/CloudFunctions.png",
“action”: {Action Data}
}
}
Button (Image Button) {
“type”: “button”,
“title”: “optional, text show on button”,
“subTitle”: “optional, short gray text”,
“data” : {
“type”: “imageButton”,
“iconUrl” : “https://ssl.pstatic.net/CloudFunctions.png",
“action”: {Action Data}
}
}
  • Button 컴포넌트 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true button
title string false text show on button
subTitle string false short gray text
data.type string true basic or imageButton
data.iconUrl string false button icon url, must be https url
data.action Action true the action of click on button

Composite Component

composite component 는 Template과 Carousel 컴포넌트로 구성됩니다.

각 특징에 맞게 챗봇의 응답을 챗봇 빌더에서 설정할 수 있으며, 챗봇 빌더를 통해 설정한 응답 결과가 아래의 json 형태로 응답됩니다.

  1. Template Component

템플릿은 기본 구성 요소로 구성됩니다. 템플릿에는 cover, contentTable, footTable의 세 부분이 있습니다. cover는 주요 내용입니다. contentTable, footTable은 테이블 레이아웃입니다.

  • Template Component 구조

new template general

  • Template Component의 Json
구분 Json Model - Example
Template {
“type”: “template”,
“title”: “optional, short bold text”,
“subTitle”: “optional, short gray text”,
“data”:{
“cover”:{ // any basic component },
“contentTableShowRows”: 3, // if row count more than 3, should be fold “contentBackgroundImage”:“https://ssl.pstatic.net/CloudFunctions.png", // optinal
“contentTable”:[ // table layout [ // first row {
“colSpan”: 1,
“rowSpan”: 2,
“data”:{ // any basic component type } },
// other cells in first row ], // another rows ],
“footTableShowRows”:3, // if row count more than 3, should be fold
“footBackgroundImage”:“https://ssl.pstatic.net/CloudFunctions.png", // optinal
“footTable”:[ // table layout same as contentTable ] }
}
  • Template Comonent 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true template
title string false short bold text
data.contentBackgroundImage string false 컨텐츠 테이블 영역에 배경 이미지 표시
data.contentTable array[][Cell] false 테이블 레이아웃, 2 차원 셀 배열이며, 셀 데이터는 기본 구성 요소입니다
data.footTableShowRows integer false 최대 행 수를 설정합니다. 행 수가 최대 값보다 많으면 최대 행을 표시하고 접어야 하며 확장 버튼을 추가해야 합니다. 설정하지 않으면 모든 행이 표시됩니다
data.footBackgroundImage string false foot 테이블 영역에 배경 이미지 표시
data.footTable array[][Cell] false contentTable과 동일하지만 contentTable이 지원할 수없는 경우가 아니면 항상 존재하지 않습니다
  • table layout의 상세 설명
필드 이름 데이터 타입 필수 여부 설명
rowSpan integer true span row count
colSpan integer true span column count
data Basic Component true Text / Image / Button
  1. Carousel Component

캐로셀 답변을 설정할 수 있습니다.

new carousel general

  • Carousel Component의 Json
구분 Json Model - Example
Carousel {
“type”: “carousel”,
“title”: “optional, short bold text”,
“subTitle”: “optional, short gray text”,
“data” : {
“cards”: [ { // any component except carousel self and flex }, // more components
] }
}
  • Carousel Component 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true carousel

Flex Component

모든 json 객체 형식을 지원합니다. 필요한 자체 JSON 사양을 정의 할 수 있습니다. 예 : FlexMessageContainerObject json을 사용할 수 있습니다.

  • Json in Flex
assortment Json Model - Example
Flex {
“type”: “flex”,
“title”: “not used”,
“subTitle”: “required, alternative text”,
“data” : { // any json object }
}
  • Flex Component Details
Field names Data type Required Explanation
type string true flex
title string true alternative text, show in chat list and push alert
subTitle string false not used
data json object true any json object. Example: line flex messsage could copy json from Flex Message Simulator

Special Messenger Component

  1. LineFlex

Line Messenger에서 지원하는 LineFlex 기능은 공통 Flex Component 형식으로 변경되었으며, 스펙은 동일합니다.

  1. LineSticker

라인 메신저에서 지원하는 LineSticker기능은 sticker list의 형식을 따라서 스티커 메시지를 설정할 수 있습니다.

  • LineSticker의 Json
구분 Json Model - Example
LineSticker {
“type”: “line_sticker”,
“data” : {
“packageId”: “packageId of LINE”,
“stickerId”: “stickerId of LINE”
}
}
  • LineSticker 컴포넌트 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true line_sticker
data.packageId string true sticker’s packageId of LINE, refer to sticker list
data.stickerId string true sticker’s stickerId of LINE, refer to sticker list
  1. LineWorksSticker

라인 웍스에서 지원하는 LineWorksSticker 기능은 sticker list의 형식을 따라서 스티커 메시지를 설정할 수 있습니다.

  • LineWorksSticker의 Json
구분 Json Model - Example
LineWorksSticker {
“type”: “lineworks_sticker”,
“data” : {
“packageId”: “packageId of LINEWORKS”,
“stickerId”: “stickerId of LINEWORKS”
}
}
  • LineWorksSticker 컴포넌트 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true lineworks_sticker
data.packageId string true sticker’s packageId of LINEWORKS, refer to sticker list
data.stickerId string true sticker’s stickerId of LINEWORKS, refer to sticker list

Action

Action은 모든 구성요소에 대한 공통 데이터입니다. Component를 클릭했을 때 수행할 작업을 정의합니다.

  1. Postback

컴포넌트를 클릭하면 postbackText를 챗봇에 포스트 백하고 포스트 백을 사용자 채팅으로 표시합니다.

  • Postback의 Json
구분 Json Model - Example
Postback { “type”: “line_sticker”, “data” : { “packageId”: “packageId of LINE”, “stickerId”: “stickerId of LINE” } }
  • Postback 컴포넌트 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true postback
data.postback string true text show as user chat, if send this field to chatbot, not affect previous features, but will not support some new features
data.postbackFull string true postback full content send to chatbot
  1. Utterance

구성 요소를 클릭하면 텍스트를 챗봇에 포스트 백(Postback)하고 텍스트를 사용자 채팅으로 표시합니다.

  • Utterance의 Json
구분 Json Model - Example
Utterance {
“type”: “utterance”,
“data” : {
“utteranceId” : 1,
“text” : “text show in chat window”,
“postback” : “postback text”
}
}
  • Utterance Component 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true utterance
data.utteranceId string true
data.text string true text show in chat window as user input
data.postback string true postback text send to chatbot
  1. Link

컴포넌트를 클릭하면 URL로 이동합니다.

  • Link의 Json
구분 Json Model - Example
Link {
“type”: “link”,
“data” : {
“url” : “http://www.ncloud.com",
“mobileUrl” : “http://m.ncloud.com"
}
}
  • Link Component 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true postback
data.url string true open url
data.mobileUrl string false url for mobile device
  1. Phone

컴포넌트를 클릭하면 다이얼 페이지로 이동합니다. 모바일에서만 지원됩니다.

  • Phone Component의 Json
구분 Json Model - Example
Phone {
“type”: “phone”,
“data” : { “number” : “400-1111-1111”, “name” : “Customer service” }
}
  • Phone Component 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true phone
data.number string true phone number
data.name string false contact name
  1. Welcome

컴포넌트를 클릭하면 open event가 전송됩니다.

  • Welcome Component의 Json
구분 Json Model - Example
Welcome { “type”: “welcome”, “data” : { “postback” : “postback text, optional” } }
  • Welcome Component 상세 설명
필드 이름 데이터 타입 필수 여부 설명
type string true welcome
data.postback string false 포스트 백 텍스트가 열린 이벤트에서 챗봇으로 전송

Quick Button

채팅 창 하단에있는 그룹 고정 버튼입니다.

Persistent Menu

고정 메뉴는 사용자가 채팅 바에서 메뉴 버튼을 터치하면 표시됩니다. 항상 환영 응답에 포함됩니다. utils를 변경하지 않음 다른 응답에 permanentMenu 컨텐츠가 포함되어 있습니다.

image

Persistent menu is a Template Component, with these definition:

  • title will show on the chat bar.
  • no cover, cover should be discard.
  • no foot table, fields relate to foot area should be discard.
  • contentBackgroundImage is the background, if the components in the contentTable have image, will cover the background.

오류 코드

오류 응답에 대해서 설명합니다.

HttpStatusCode Description
500 Internal server error
  • Error Response Body:
 {
    "code": "1001",
    "message": "domain code test not found",
    "timestamp": 12345678
  }
  • Body Introduce
Field Type Must Exists Description
code string true error code
event string true fixed string value “send”
timestamp long true response time milliseconds since January 1, 1970, 00:00:00 GMT

오류 코드

ErrorCode Description
4000 request param invalid
4010 Unauthorized
4030 Forbidden to access
4031 Signature validate failed
4032 timestamp exceeded time window(10000ms)
1000 version not support
1001 Not found domain code
1002 check url param is invalid
5000 Unknown service error
5010 Current protocol version not support this reply structure

API 예제

다음은 각 언어별 Custom API 구현 예제입니다

  • JAVA - Chatbot Custom API call source
package com.ncp.ai.demo.process;

import android.media.MediaPlayer;
import android.os.Environment;
import android.util.Base64;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.Timestamp;
import java.util.Date;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import android.util.Base64;

import org.json.JSONArray;
import org.json.JSONObject;

public class ChatbotProc {

  public static String main(String voiceMessage, String apiURL, String secretKey) {


        String chatbotMessage = "";

        try {
            //String apiURL = "https://ex9av8bv0e.apigw.ntruss.com/custom_chatbot/prod/";

            URL url = new URL(apiURL);

            String message = getReqMessage(voiceMessage);
            System.out.println("##" + message);

            String encodeBase64String = makeSignature(message, secretKey);

            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("POST");
            con.setRequestProperty("Content-Type", "application/json;UTF-8");
            con.setRequestProperty("X-NCP-CHATBOT_SIGNATURE", encodeBase64String);

            // post request
            con.setDoOutput(true);
            DataOutputStream wr = new DataOutputStream(con.getOutputStream());
            wr.write(message.getBytes("UTF-8"));
            wr.flush();
            wr.close();
            int responseCode = con.getResponseCode();

            BufferedReader br;

            if(responseCode==200) { // Normal call
                System.out.println(con.getResponseMessage());

                BufferedReader in = new BufferedReader(
                        new InputStreamReader(
                                con.getInputStream()));
                String decodedString;
                while ((decodedString = in.readLine()) != null) {
                    chatbotMessage = decodedString;
                }
                //chatbotMessage = decodedString;
                in.close();

            } else {  // Error occurred
                chatbotMessage = con.getResponseMessage();
            }
        } catch (Exception e) {
            System.out.println(e);
        }

        return chatbotMessage;
    }

    public static String makeSignature(String message, String secretKey) {

        String encodeBase64String = "";

        try {
            byte[] secrete_key_bytes = secretKey.getBytes("UTF-8");

            SecretKeySpec signingKey = new SecretKeySpec(secrete_key_bytes, "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);

            byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
            encodeBase64String = Base64.encodeToString(rawHmac, Base64.NO_WRAP);

            return encodeBase64String;

        } catch (Exception e){
            System.out.println(e);
        }

        return encodeBase64String;

    }

    public static String getReqMessage(String voiceMessage) {

        String requestBody = "";

        try {

            JSONObject obj = new JSONObject();

            long timestamp = new Date().getTime();

            System.out.println("##"+timestamp);

            obj.put("version", "v2");
            obj.put("userId", "U47b00b58c90f8e47428af8b7bddc1231heo2");
//=> userId is a unique code for each chat user, not a fixed value, recommend use UUID. use different id for each user could help you to split chat history for users.

            obj.put("timestamp", timestamp);

            JSONObject bubbles_obj = new JSONObject();

            bubbles_obj.put("type", "text");

            JSONObject data_obj = new JSONObject();
            data_obj.put("description", voiceMessage);

            bubbles_obj.put("type", "text");
            bubbles_obj.put("data", data_obj);

            JSONArray bubbles_array = new JSONArray();
            bubbles_array.put(bubbles_obj);

            obj.put("bubbles", bubbles_array);
            obj.put("event", "send");

            requestBody = obj.toString();

        } catch (Exception e){
            System.out.println("## Exception : " + e);
        }

        return requestBody;

    }
}
  • Example of calling ChatbotProc.java above in UI stage
package com.example.user.ncpaidemo;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.speech.tts.Voice;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.naver.speech.clientapi.SpeechRecognitionResult;
import com.ncp.ai.demo.process.ChatbotProc;
import com.ncp.ai.demo.process.CsrProc;
import com.ncp.ai.demo.process.CssProc;
import com.ncp.ai.utils.AudioWriterPCM;

import org.json.JSONArray;
import org.json.JSONObject;

import java.lang.ref.WeakReference;
import java.util.List;

public class VoiceChatbotActivity extends BaseActivity {

    private static final String TAG = VoiceChatbotActivity.class.getSimpleName();
    private RecognitionHandler handler;
    private CsrProc naverRecognizer;
    private TextView txtResult;
    private Button btnStart;
    private String mResult;
    private AudioWriterPCM writer;
    private String clientId;
    private String clientSecret;
    // Handle speech recognition Messages.
    private void handleMessage(Message msg) {
        switch (msg.what) {
            case R.id.clientReady: // Voice recognition ready
                txtResult.setText("Connected");
                writer = new AudioWriterPCM(Environment.getExternalStorageDirectory().getAbsolutePath() + "/NaverSpeechTest");
                writer.open("Test");
                break;
            case R.id.audioRecording:
                writer.write((short[]) msg.obj);
                break;
            case R.id.partialResult:
                mResult = (String) (msg.obj);
                mResult += mResult;
                txtResult.setText(mResult);
                break;
            case R.id.finalResult: // Final recognition result
                SpeechRecognitionResult speechRecognitionResult1 = (SpeechRecognitionResult) msg.obj;
                List<String> results1 = speechRecognitionResult1.getResults();
                StringBuilder strBuf1 = new StringBuilder();
                for(String result : results1) {
                    strBuf1.append(result);
                    //strBuf.append("\n");
                    break;
                }
                mResult = strBuf1.toString();
                txtResult.setText(mResult);

                requestChatbot();

                break;
            case R.id.recognitionError:
                if (writer != null) {
                    writer.close();
                }
                mResult = "Error code : " + msg.obj.toString();
                txtResult.setText(mResult);
                btnStart.setText(R.string.str_start);
                btnStart.setEnabled(true);
                break;
            case R.id.clientInactive:
                if (writer != null) {
                    writer.close();
                }
                btnStart.setText(R.string.str_start);
                btnStart.setEnabled(true);
                break;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_voice_chatbot);
        SharedPreferences sharedPref = getSharedPreferences("PREF", Context.MODE_PRIVATE);

        clientId = sharedPref.getString("application_client_id", "");
        clientSecret = sharedPref.getString("application_client_secret", "");

        txtResult = (TextView) findViewById(R.id.textViewVoiceChatbotResult);
        btnStart = (Button) findViewById(R.id.btn_voice_chatbot1);
        handler = new RecognitionHandler(this);
        //naverRecognizer = new CsrProc(this, handler, clientId);
        naverRecognizer = CsrProc.getCsrProc(this, clientId);
        naverRecognizer.setHandler(handler);
        btnStart.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                if (!naverRecognizer.getSpeechRecognizer().isRunning()) {

                    mResult = "";
                    txtResult.setText("Connecting...");
                    btnStart.setText(R.string.str_stop);
                    naverRecognizer.recognize();
                } else {
                    Log.d(TAG, "stop and wait Final Result");
                    btnStart.setEnabled(false);
                    naverRecognizer.getSpeechRecognizer().stop();
                }
            }
        });

        Button voiceChatbotReplay;

        voiceChatbotReplay = (Button) findViewById(R.id.btn_voice_chatbot_replay);
        voiceChatbotReplay.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {

                TextView txtResult = (TextView) findViewById(R.id.text_voice_chatbot_replay);
                CSSExecute(txtResult.getText().toString());

            }
        });
    }

    private void requestChatbot() {

        SharedPreferences sharedPref = getSharedPreferences("PREF", Context.MODE_PRIVATE);

        String chatbotApiGwUrl = sharedPref.getString("chatbot_api_gw_url", "");
        String chatbotSecretKey = sharedPref.getString("chatbot_secret_key", "");

        TextView csrSourceText = (TextView)findViewById(R.id.textViewVoiceChatbotResult);
        String text = csrSourceText.getText().toString();

        VoiceChatbotActivity.VoiceChatbotTask task = new VoiceChatbotActivity.VoiceChatbotTask();
        task.execute(text, chatbotApiGwUrl, chatbotSecretKey);
    }

    @Override
    protected void onStart() {
        super.onStart(); // Voice recognition server initialization is here
        naverRecognizer.getSpeechRecognizer().initialize();
    }
    @Override
    protected void onResume() {
        super.onResume();
        mResult = "";
        txtResult.setText("");
        btnStart.setText(R.string.str_start);
        btnStart.setEnabled(true);
    }
//    @Override
//    protected void onStop() {
//        System.out.println("voice chatbot End!!!");
//        super.onStop(); // terminate voice recognition server
//        naverRecognizer.getSpeechRecognizer().release();
//    }
    // Declare handler for handling SpeechRecognizer thread's Messages.
    static class RecognitionHandler extends Handler {
        private final WeakReference<VoiceChatbotActivity> mActivity;
        RecognitionHandler(VoiceChatbotActivity activity) {
            mActivity = new WeakReference<VoiceChatbotActivity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            VoiceChatbotActivity activity = mActivity.get();
            if (activity != null) {
                activity.handleMessage(msg);
            }
        }
    }

    public class VoiceChatbotTask extends AsyncTask<String, String, String> {

        @Override
        public String doInBackground(String... strings) {

            return ChatbotProc.main(strings[0], strings[1], strings[2]);
        }

        @Override
        protected void onPostExecute(String result) {

            ReturnThreadResult(result);
        }
    }

    public String ReturnThreadResult(String result) {

        //{"version":"v2","userId":"U47b00b58c90f8e47428af8b7bddc1231heo2","sessionId":"617666","timestamp":1546593912020,
        // "bubbles":[{"type":"template","data":{"cover":{"type":"text","data":{"description":"b"}},"contentTable":[[{"rowSpan":1,"colSpan":1,"data":{"type":"button","title":"b","data":{"type":"basic","action":{"type":"link","data":{"url":"https://www.ncloud.com/product"}}}}}],[{"rowSpan":1,"colSpan":1,"data":{"type":"button","title": "b","data":{"type":"basic","action":{"type":"link","data":{"url":"https://www.ncloud.com/product"}}}}}]]}}],"event":"send"}

        //{"version":"v2","userId":"U47b00b58c90f8e47428af8b7bddc1231heo2","sessionId":"641799","timestamp":1546777198124,
        // "bubbles":[{"type":"text","data":{"description":"b"}}],"event":"send"}
        String chatbotMessage =  "";
        String rlt = result;
        try{
            JSONObject jsonObject = new JSONObject(rlt);
            JSONArray bubbles = jsonObject.getJSONArray("bubbles");

            for (int i =0; i < bubbles.length(); i++){

                JSONObject bubble = bubbles.getJSONObject(i);

                String chatType = bubble.getString("type");

                if (chatType.equals("text")){

                    chatbotMessage = bubble.getJSONObject("data").getString("description");

                }else if (chatType.equals("template")) {

                    chatbotMessage = bubble.getJSONObject("data").getJSONObject("cover").getJSONObject("data").getString("description");

                }else {
                    chatbotMessage = "";
                }

                TextView txtResult = (TextView) findViewById(R.id.text_voice_chatbot_replay);
                txtResult.setText(chatbotMessage);

                break;
            }

        } catch (Exception e) {
            System.out.println(e);
        }

        CSSExecute(chatbotMessage);

        return chatbotMessage;
    }

    private void CSSExecute(String message) {

        VoiceChatbotActivity.NaverTTSTask tts = new VoiceChatbotActivity.NaverTTSTask();
        tts.execute(message, "mijin", clientId, clientSecret);
    }

    public class NaverTTSTask extends AsyncTask<String, String, String> {

        @Override
        public String doInBackground(String... strings) {
            System.out.println(strings[1]);
            CssProc.main(strings[0], strings[1], strings[2], strings[3]);
            return null;
        }
    }
}

  • Python - Chatbot Custom API v2
import hashlib
import hmac
import base64
import time
import requests
import json


class ChatbotMessageSender:

    # chatbot api gateway url
    ep_path = ''
    # chatbot custom secret key
    secret_key = ''

    def req_message_send(self):

        timestamp = self.get_timestamp()
        request_body = {
            'version': 'v2',
            'userId': 'U47b00b58c90f8e47428af8b7bddcda3d1111111',
            'timestamp': timestamp,
            'bubbles': [
                {
                    'type': 'text',
                    'data': {
                        'description': 'About Me'
                    }
                }
            ],
            'event': 'send'
        }

        ## Request body
        encode_request_body = json.dumps(request_body).encode('UTF-8')

        ## make signature
        signature = self.make_signature(self.secret_key, encode_request_body)

        ## headers
        custom_headers = {
            'Content-Type': 'application/json;UTF-8',
            'X-NCP-CHATBOT_SIGNATURE': signature
        }

        print("## Timestamp : ", timestamp)
        print("## Signature : ", signature)
        print("## headers ", custom_headers)
        print("## Request Body : ", encode_request_body)

        ## POST Request
        response = requests.post(headers=custom_headers, url=self.ep_path, data=encode_request_body)

        return response

    @staticmethod
    def get_timestamp():
        timestamp = int(time.time() * 1000)
        return timestamp

    @staticmethod
    def make_signature(secret_key, request_body):

        secret_key_bytes = bytes(secret_key, 'UTF-8')

        signing_key = base64.b64encode(hmac.new(secret_key_bytes, request_body, digestmod=hashlib.sha256).digest())

        return signing_key


if __name__ == '__main__':

    res = ChatbotMessageSender().req_message_send()

    print(res.status_code)
    if(res.status_code == 200):
        print(res.text)
        #print(res.read().decode("UTF-8"))
  • php

function makeSignature($secretKey, $requestBody) {
  $signautue = base64_encode(hash_hmac('sha256', $requestBody, $secretKey,true));
  //echo "this is signiture : ".$signautue."\n";
  return $signautue;
}


try {
  // User IP
  if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR']) {
      $clientIpAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
  } else {
      $clientIpAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
  }


  $timestamp = "";
  $microtime = "";
  list($microtime,$timestamp) = explode(' ',microtime());
  $timestamp = $timestamp.substr($microtime, 2, 3);

  $url = "https://xi3mfpym2x.apigw.ntruss.com/send/beta/";

  $requestBody=  '{
    "version": "v2",
    "userId": "U47b00b58c90f8e47428af8b7bddcda3d231",
    "userIp": "'.$clientIpAddress.'",
    "timestamp": '.$timestamp.',
    "bubbles": [
      {
        "type": "text",
        "data" : {
          "description" : "postback text of welcome action"
        }
      }
    ],
    "event": "open"
  }';

  $secretKey = "YnVFTWZDemt3bUZhaEJlalN1Z3ZNY2pzeVp0aVRjd04=12";
  $signautue = makeSignature($secretKey,$requestBody);

  $is_post = true;

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
  curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody);
  $headers = array();
  $headers[] = "Content-Type:application/json; charset=utf-8";
  $headers[] = "X-NCP-CHATBOT_SIGNATURE: " .$signautue;
  curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

  $response = curl_exec($ch);
  $err = curl_error($ch);
  curl_close ($ch);
  if ($err) {
    echo "cURL Error #:" . $err;
  } else {
    echo $response;
  }

} catch(Exception $E) {
    echo "Response: ". $E->lastResponse . "\n";
}
?>
  • swift
import UIKit
import CommonCrypto


// chatbot custom secret key
let secret_key = "";

// chatbot api gateway url
let invoke_url = "";

class ViewController: UIViewController, URLSessionDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        demo()
    }

    func demo() {
        let timeInterval: TimeInterval = NSDate().timeIntervalSince1970
        let millisecond = CLongLong(round(timeInterval*1000))
        
        let body = ["bubbles":[["data": ["description":"test"],
                                "type":"text"]],
                    "event":"send",
                    "timestamp":millisecond,
                    "userId":"test",
                    "version":"v2"] as NSDictionary
       
        do {
            let jsonDataFromBody = try JSONSerialization.data(withJSONObject: body, options: JSONSerialization.WritingOptions.prettyPrinted)
            let jsonBytes = [CUnsignedChar](jsonDataFromBody)
            let jsonBytesPointer = UnsafePointer<CUnsignedChar>(jsonBytes) //UnsafeRawPointer
            
            let cKey = secret_key.cString(using: String.Encoding.utf8)
            let digestLen = Int(CC_SHA256_DIGEST_LENGTH)
            let sha256 = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
            CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), cKey, secret_key.lengthOfBytes(using: String.Encoding.utf8), jsonBytesPointer, jsonBytes.count, sha256)
            
            let base64_sha256 = Data.init(bytes: sha256, count: Int(CC_SHA256_DIGEST_LENGTH)).base64EncodedString()
            
            httpsRequest("X-NCP-CHATBOT_SIGNATURE", base64_sha256, body: jsonDataFromBody)
            sha256.deallocate()
            
        } catch {
            print(error)
        }
        
    }
    
    func httpsRequest(_ signHeaderKey:String, _ signature:String, body:Data) {
        let url = URL.init(string: invoke_url)!
        var request = URLRequest.init(url: url)
        request.addValue(signature, forHTTPHeaderField: signHeaderKey)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        request.httpBody = body
     
        let session = URLSession.shared
        let task = session.dataTask(with: request) { (data:Data?, response:URLResponse?, error:Error?) in
            print(String.init(data:data!, encoding: .utf8)!)
        }
        task.resume()
    }
    
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        if challenge.protectionSpace.authenticationMethod != NSURLAuthenticationMethodServerTrust {
            return
        }
        let credential = URLCredential.init(trust: challenge.protectionSpace.serverTrust!)
        completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
    }

}
  • objective-c
#import "ViewController.h"
#import <UIKit/UIKit.h>
#import <CommonCrypto/CommonHMAC.h>

// chatbot custom secret key
static NSString *secret_key = @"";

// chatbot api gateway url
static NSString *invoke_url = @"";

@interface ViewController () <NSURLSessionDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self demo];
}

- (void)demo {
    NSTimeInterval time = [[NSDate date] timeIntervalSince1970]*1000;
    long long millisecond = [[NSNumber numberWithDouble:time] longLongValue];
    NSDictionary *body = @{@"bubbles":@[@{@"data":@{@"description":@"test"},
                                          @"type":@"text"}],
                           @"event":@"send",
                           @"timestamp":millisecond,
                           @"userId":@"U47b00b58c90f8e47428af8b7bddcda3d1111111",
                           @"version":@"v2"
                           };
    NSError *error;

    NSData *jsondataFrombody = [NSJSONSerialization dataWithJSONObject:body
                                                           options:0
                                                             error:&error];
    
    const char *cKey = [secret_key cStringUsingEncoding:NSUTF8StringEncoding];
    NSMutableData* sha256 = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
    CCHmac(kCCHmacAlgSHA256, cKey, secret_key.length, jsondataFrombody.bytes, jsondataFrombody.length, sha256.mutableBytes);
    
    NSString *base64_sha256 = [sha256 base64EncodedStringWithOptions:0];
    
    [self httpsRequest:@"X-NCP-CHATBOT_SIGNATURE" signature:base64_sha256 bodyData:jsondataFrombody];
}

- (void)httpsRequest:(NSString *)signHeaderKey signature:(NSString *)sign bodyData:(NSData *)bodyData  {
    NSURL *url = [NSURL URLWithString:invoke_url];
    NSMutableURLRequest *mRequest = [NSMutableURLRequest requestWithURL:url];
    [mRequest setValue:sign forHTTPHeaderField:signHeaderKey];
    [mRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    mRequest.HTTPMethod = @"POST";
    mRequest.HTTPBody = bodyData;
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                                          delegate:self
                                                     delegateQueue:[NSOperationQueue mainQueue]];//replace delegateQueue with your workingQueue
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:mRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"response: %@", [response description]);
        NSLog(@"response data:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    }];
    [dataTask resume];

}

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
    if (![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"]) {
        return;
    }
    NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
    completionHandler(NSURLSessionAuthChallengeUseCredential,credential);

}

@end