CLOVA Chatbot Custom API
    • PDF

    CLOVA Chatbot Custom API

    • PDF

    기사 요약

    요청

    MethodRequest URI
    POSTCLOVA 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-Typeapplication/json;UTF-8

    Signature 생성

    CLOVA 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 요청 필드 상세 설명

    필드 이름데이터 타입필수여부설명
    versionstringfalseversion은 'v2'가 기본이며, 값이 입력되지 않은 경우 'v1'으로 설정
    userIdstringtrue봇과 채팅하는 사용자의 고유 ID로 최대 256자를 넘지 않아야 함(사용자마다 고유한 userId를 설정 필요)
    userIpstringfalse사용자의 Ip Adress 이며, 필수는 아님
    timestamplongtrue- 타임스탬프 값
    January 1, 1970, 00:00:00 GMT
    bubblesarraytrueempty array "[]" or only one Text component caused by welcome action
    eventstringtrue이벤트 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 요청 필드 상세 설명

    필드 이름데이터 타입필수여부설명
    versionstringfalseversion은 'v2'가 기본이며, 값이 입력되지 않은 경우 'v1'으로 설정
    userIdstringtrue봇과 채팅하는 사용자의 고유 ID로 최대 256자를 넘지 않아야 함(사용자마다 고유한 userId를 설정)
    userIpstringfalse사용자의 Ip Adress 이며, 필수는 아님
    timestamplongtrue- 타임스탬프 값
    January 1, 1970, 00:00:00 GMT
    bubblesarray[Text]true하나의 Text Component 만 지원
    하나 이상의 Text Component가 있을 경우, 마지막 Component를 사용자 요청 사용
    eventstringtrue이벤트 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요청 필드 상세 설명

    필드 이름데이터 타입필수여부설명
    versionstringfalseversion은 'v2'가 기본이며, 값이 입력되지 않은 경우 'v1'으로 설정
    userIdstringtrue봇과 채팅하는 사용자의 고유 ID로 최대 256자를 넘지 않아야 함(사용자마다 고유한 userId를 설정)
    userIpstringfalse사용자의 Ip Adress 이며, 필수는 아님
    timestamplongtrue- 타임스탬프 값
    January 1, 1970, 00:00:00 GMT
    bubblesarraytrue빈 Array 값으로 설정 "[]"
    eventstringtrue이벤트 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"
    }

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

    필드 이름데이터 타입필수여부설명
    versionstringfalseversion은 'v2'가 기본이며, 값이 입력되지 않은 요청의 경우 'v1'으로 응답됨
    userIdstringtrue요청 시 설정한 userId 값과 동일
    sessionIdstringfalse현재 session id 이며, CLOVA Chatbot에서 관리하는 값
    timestamplongtrue응답 타임스탬프 값 (milliseconds , January 1, 1970, 00:00:00 GMT)
    bubblesarray[Component]false응답 컴포넌트의 배열(각 컴포넌트는 챗봇의 응답 Bubble과 매치)
    quickButtonsarray[Component]false챗봇 하단에 설정된 고정버튼(quickButton)의 정보
    scenariojsonObjectfalse- 사용자의 질의에 일치된 시나리오 분석 결과
    시나리오 이름 및 의도(대화유형) 정보 제공
    entitiesarray[jsonObject]false사용자의 질의에 일치된 엔티티(entity)분석 결과
    keywordsarray[jsonObject]false사용자 채팅에서 키워드와 일치하는 단어 키워드에는 "exactMatch"또는 "contain", "exactMatch"의 두 가지 유형 존재
    "exactMatch"는 사용자 입력이 키워드와 완전히 일치 함을 의미하고 "contain"은 사용자 입력에 키워드가 포함됨을 의미
    persistentMenuTemplate Componentfalsepersistent menu를 설정합니다.PersistentMenu
    eventstringtrue고정된 응답 "send"

    new general

    Error

    Error

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

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

    HttpStatusCodeDescription
    500Internal 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 컴포넌트 구조

    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 컴포넌트 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtruetext
    titlestringfalseshort bold text
    subTitlestringfalseshort gray text
    data.descriptionstringfalsea long text content
    data.urlstringfalsethe hyperlink jump url
    data.urlAliasstringfalsethe hyperlink show text
    data.actionActionfalsethe action of click on text or title

    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 컴포넌트 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtrueimage
    titlestringfalseshort bold text
    subTitlestringfalseshort gray text
    data.imageUrlstringtrueimage url, must be https url
    data.altstringfalseshort hint text show hover on image
    data.imagePositionstringfalsetop / bottom / left / right, default is top
    data.descriptionstringfalsedetails info of image
    data.urlstringfalsethe hyperlink jump url
    data.urlAliasstringfalsethe hyperlink show text
    data.actionActionfalsethe action of click on image or title

    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 컴포넌트 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtruebutton
    titlestringfalsetext show on button
    subTitlestringfalseshort gray text
    data.typestringtruebasic or imageButton
    data.iconUrlstringfalsebutton icon url, must be https url
    data.actionActiontruethe action of click on button

    Composite Component

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

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

    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 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtruetemplate
    titlestringfalseshort bold text
    data.contentBackgroundImagestringfalse컨텐츠 테이블 영역에 배경 이미지 표시
    data.contentTablearray[][Cell]false테이블 레이아웃, 2 차원 셀 배열이며, 셀 데이터는 기본 구성 요소
    data.footTableShowRowsintegerfalse최대 행 수를 설정
    행 수가 최대 값보다 많으면 최대 행을 표시하고 접어야 하며 확장 버튼을 추가해야 함
    설정하지 않으면 모든 행이 표시됨
    data.footBackgroundImagestringfalsefoot 테이블 영역에 배경 이미지 표시
    data.footTablearray[][Cell]falsecontentTable과 동일하지만 contentTable이 지원할 수없는 경우가 아니면 항상 존재하지 않음
    • table layout의 상세 설명
    필드 이름데이터 타입필수 여부설명
    rowSpanintegertruespan row count
    colSpanintegertruespan column count
    dataBasic ComponenttrueText / Image / Button

    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 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtruecarousel

    Flex Component

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

    • Json in Flex
    assortmentJson Model - Example
    Flex{
    "type": "flex",
    "title": "not used",
    "subTitle": "required, alternative text",
    "data" : { // any json object }
    }
    • Flex Component Details
    Field namesData typeRequiredExplanation
    typestringtrueflex
    titlestringtruealternative text, show in chat list and push alert
    subTitlestringfalsenot used
    datajson objecttrueany json object. Example: line flex messsage could copy json from Flex Message Simulator

    Special Messenger Component

    1. LineFlex

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

    2. LineSticker

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

      • LineSticker의 Json
    구분Json Model - Example
    LineSticker{
    "type": "line_sticker",
    "data" : {
    "packageId": "packageId of LINE",
    "stickerId": "stickerId of LINE"
    }
    }
    • LineSticker 컴포넌트 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtrueline_sticker
    data.packageIdstringtruesticker's packageId of LINE, refer to sticker list
    data.stickerIdstringtruesticker'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 컴포넌트 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtruelineworks_sticker
    data.packageIdstringtruesticker's packageId of LINEWORKS, refer to sticker list
    data.stickerIdstringtruesticker'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 컴포넌트 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtruepostback
    data.postbackstringtruetext show as user chat, if send this field to chatbot, not affect previous features, but will not support some new features
    data.postbackFullstringtruepostback 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 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtrueutterance
    data.utteranceIdstringtrue
    data.textstringtruetext show in chat window as user input
    data.postbackstringtruepostback 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 상세 설명
    필드 이름데이터 타입필수 여부설명
    typestringtruepostback
    data.urlstringtrueopen url
    data.mobileUrlstringfalseurl for mobile device
    1. Phone

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

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

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

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

    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.

    오류 코드

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

    HttpStatusCodeDescription
    500Internal server error
    • Error Response Body:
     {
        "code": "1001",
        "message": "domain code test not found",
        "timestamp": 12345678
      }
    
    • Body Introduce
    FieldTypeMust ExistsDescription
    codestringtrueerror code
    eventstringtruefixed string value "send"
    timestamplongtrueresponse time milliseconds since January 1, 1970, 00:00:00 GMT

    오류 코드

    ErrorCodeDescription
    4000request param invalid
    4010Unauthorized
    4030Forbidden to access
    4031Signature validate failed
    4032timestamp exceeded time window(10000ms)
    1000version not support
    1001Not found domain code
    1002check url param is invalid
    5000Unknown service error
    5010Current 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
    

    이 문서가 도움이 되었습니까?

    Changing your password will log you out immediately. Use the new password to log back in.
    First name must have atleast 2 characters. Numbers and special characters are not allowed.
    Last name must have atleast 1 characters. Numbers and special characters are not allowed.
    Enter a valid email
    Enter a valid password
    Your profile has been successfully updated.