- 인쇄
- PDF
CLOVA Chatbot Custom API
- 인쇄
- PDF
요청
Method | Request URI |
---|---|
POST | CLOVA 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 생성
CLOVA Chatbot Custom API에서 Signature 생성 방법을 자세하게 설명하기 위해서 Server API의 getRegionList 액션을 예시로 들겠습니다.
- HTTP 호출 형태로 요청하고자 하는 액션을 문자열로 만듭니다. 이때 호출하는 API 주소는 제외가 되어야 합니다. (
https://ncloud.apigw.ntruss.com
)- GET /server/v2/getRegionList
{x-ncp-apigw-timestamp}
{x-ncp-iam-access-key}
- GET /server/v2/getRegionList
- AccessKey에 대응하는 SecretKey를 이용하여 HmacSHA256알고리즘으로 암호화를 합니다.
- 한글이 들어갈 경우 글이 깨지는 경우가 발생하기 때문에 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
CLOVA 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 이며, CLOVA 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" |
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가지의 기본 컴포넌트를 제공합니다.
Text
챗봇의 답변할 Text 컴포넌트 형태이며, 타이틀, 서브 타이틀 그리고, 자세한 설명과 URL을 설정할 수 있습니다.
- 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 |
Image
Image를 포함한 답변 컴포넌트 형태이며, 이미지와 함께 타이틀, 서브 타이틀 그리고, 자세한 설명과 URL을 설정할 수 있습니다.
- 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 |
Button
Image를 포함한 답변 컴포넌트 형태이며, 이미지와 함께 타이틀, 서브 타이틀 그리고, 자세한 설명과 URL을 설정할 수 있습니다.
- 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 형태로 응답됩니다.
Template Component
템플릿은 기본 구성 요소로 구성됩니다. 템플릿에는 cover, contentTable, footTable의 세 부분이 있습니다. cover는 주요 내용입니다. contentTable, footTable은 테이블 레이아웃입니다.
- Template Component 구조
- 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 |
Carousel Component
캐로셀 답변을 설정할 수 있습니다.
- 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
LineFlex
Line Messenger에서 지원하는 LineFlex 기능은 공통 Flex Component 형식으로 변경되었으며, 스펙은 동일합니다.
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 |
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를 클릭했을 때 수행할 작업을 정의합니다.
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 |
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 |
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 |
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 |
컴포넌트를 클릭하면 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 컨텐츠가 포함되어 있습니다.
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 - CLOVA 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 - CLOVA 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