CHZZK
  • ์‹œ์ž‘ํ•˜๊ธฐ
    • About CHZZK Developers
    • ๐Ÿ†•Updates
  • CHZZK API
    • Authorization
    • ์ฐธ๊ณ ์‚ฌํ•ญ
    • Session
    • User
    • Channel
    • Category
    • Live
    • Chat
    • Drops
  • DROPS
    • ๋“œ๋กญ์Šค ๊ฐœ์š”
    • ๋“œ๋กญ์Šค ์—ฐ๋™ ๋ฐ ์ ˆ์ฐจ
    • Webhook Event
    • ๋“œ๋กญ์Šค ์บ ํŽ˜์ธ ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ ๊ฐ€์ด๋“œ
  • Resources
    • Brand Guides
    • Chzzk AD
Powered by GitBook
On this page
  • 1.1 Event Subscription
  • 1.2 Handling Webhook Event
  • 1.2.1 Request Header
  • 1.2.2 Notification Message
  • 1.3 Event Types
  • 1.3.1 drops_reward_claim
  • 1.4 Event Message Verification
  • 1.4.1 Retry Policy
  1. DROPS

Webhook Event

์น˜์ง€์ง Webhook์„ ์ด์šฉํ•˜์—ฌ ์ด๋ฒคํŠธ ์•Œ๋ฆผ(Notification Message)์„ ๋ฐ›๋Š” ๋ฐฉ๋ฒ•์„ ์•ˆ๋‚ดํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ด๋“œ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค.

1.1 Event Subscription

์ด๋ฒคํŠธ ๋ฐœ์ƒ ์•Œ๋ฆผ์„ ๋ฐ›๊ธฐ ์œ„ํ•ด ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • Webhook์„ ์ด์šฉํ•˜์—ฌ ์ด๋ฒคํŠธ ์•Œ๋ฆผ(Notification Message)์„ ๋ฐ›๋Š” ๋ฐฉ๋ฒ•์„ ์šฐ์„ ์  ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์น˜์ง€์ง ๊ฐœ๋ฐœ์ž ์„ผํ„ฐ ์‚ฌ์šฉ ์‹ ์ฒญ๊ณผ ์ด๋ฒคํŠธ ์•Œ๋ฆผ์„ ๋ฐ›์„ Webhook URL ๋“ฑ๋ก์ด ์™„๋ฃŒ ๋˜์–ด์žˆ๋Š”์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.

1.2 Handling Webhook Event

  • POST ์š”์ฒญ Webhook URL๋กœ ์ด๋ฒคํŠธ๊ฐ€ ์ „์†ก๋˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  • Webhook URL์€ ๋ฐ˜๋“œ์‹œ SSL ์‚ฌ์šฉ๊ณผ 443 port๊ฐ€ ์—ด๋ ค์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (HTTPS ํ•„์ˆ˜)

  • Webhook POST ์š”์ฒญ ์‘๋‹ต์€ ๋ฐ˜๋“œ์‹œ ์ˆ˜ ์ดˆ ์ด๋‚ด ์‘๋‹ต์ด ์ „๋‹ฌ๋˜์–ด์•ผ ํ•˜๋ฉฐ, ๋ฉ”์‹œ์ง€ ๋ฐœ์†ก์ด ์ œ๋Œ€๋กœ ๊ฐ€์ง€ ์•Š์•˜๋‹ค๊ณ  ํŒ๋‹จ์ด ๋˜๋ฉด ์ตœ๋Œ€ 3๋ฒˆ์˜ ๋ฉ”์‹œ์ง€ ์žฌ๋ฐœ์†ก์ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. (์ž์„ธํ•œ retry ๊ด€๋ จ ์„ค๋ช…์€ Request Header์˜ Retry ํ—ค๋”๊ด€๋ จ ๋‚ด์šฉ ์ฐธ์กฐ)

  • Webhook POST ์š”์ฒญ ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ๋Š” 2XX ํ˜•ํƒœ์˜ ์ฝ”๋“œ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์™ธ๋Š” ๋ชจ๋‘ ์‹คํŒจ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค.

1.2.1 Request Header

Header
Description

Chzzk-Event-Message-Id

๋ฉ”์‹œ์ง€ ์‹๋ณ„์ž ID

Chzzk-Event-Message-Timestamp

๋ฉ”์‹œ์ง€ ๋ฐœ์†ก ์‹œ๊ฐ UTC datetime(RFC 3339)

Chzzk-Event-Message-Signature

HMAC signature, ์น˜์ง€์ง์—์„œ ๋ณด๋‚ธ ๋ฉ”์‹œ์ง€๊ฐ€ ๋งž๋Š”์ง€ ๊ฒ€์ฆ ์šฉ๋„๋กœ ์‚ฌ์šฉ ํ•„์š”

Chzzk-Event-Message-Type

message type ์œ ํ˜• ํ˜„์žฌ notification value๋กœ๋งŒ ์ œ๊ณต

Chzzk-Event-Message-Data-Type

์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ์œ ํ˜• (์ฐธ์กฐ : Event Types)

Chzzk-Event-Message-Version

message ํ˜•์‹ ๋ฒ„์ „ message.version ๊ฐ’๊ณผ ์ผ์น˜ (์ฐธ์กฐ : Notification Message)

Chzzk-Event-Message-Data-Version

eventType ์— ๋”ฐ๋ฅธ data ํ˜•์‹ ๋ฒ„์ „ message.event.version ๊ฐ’๊ณผ ์ผ์น˜ (์ฐธ์กฐ : Notification Message)

Chzzk-Event-Message-Retry

์ด๋ฒคํŠธ ์žฌ๋ฐœ์†ก ํšŸ์ˆ˜ ์ •์ƒ์ ์ธ ๊ฒฝ์šฐ ๋ณดํ†ต ํ•œ ๋ฒˆ์˜ ๋ฉ”์‹œ์ง€๋งŒ ๋ฐœ์†กํ•˜์ง€๋งŒ, ์•Œ๋ฆผ์„ ์ˆ˜์‹  ๋ฐ›๋Š” ๊ณณ์—์„œ ์ •์ƒ์ ์œผ๋กœ ์‘๋‹ตํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์„ ๊ฒฝ์šฐ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ด๋ฒคํŠธ๋ฅผ ์žฌ๋ฐœ์†กํ•จ. Chzzk-Event-Message-Idํ—ค๋”์— ๊ฐ™์€ id์˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์—ฌ๋Ÿฌ ์ฐจ๋ก€ ์žฌ๋ฐœ์†ก ๋  ์ˆ˜ ์žˆ์Œ (์ฐธ์กฐ : Retry Policy) Note : ์žฌ๋ฐœ์†ก ๊ฒฝ์šฐ์—๋งŒ Retry ํ—ค๋”๋ฅผ ์ „๋‹ฌํ•จ

1.2.2 Notification Message

  • Webhook URL๋กœ POST ์š”์ฒญ์„ ์ „์†กํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  • Reqeust body๋ฅผ JSON parsing ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • JSON parsing์„ ์™„๋ฃŒํ•  ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์‘๋‹ต ๋ฐ›์Šต๋‹ˆ๋‹ค.

์˜ˆ) ๋ฉ”์‹œ์ง€ ํ˜•ํƒœ

Chzzk-Event-Message-Type : notification

{
  "message": {
    "messageId": "eafe79192ab427be4e85e5a825c980af",
    "version": "1",
    "event": {
      "eventTimeMillis": 1722477515159,
      "version": "1",
      "eventType": "drop_reward_claim",
      "data": {
        "dropsClaimId": "97",
        "channelId": "${channelId}",
        "dropsRewardId": "2",
        "dropsCampaignId": "c91f66d879fb96e39d2f44e0d6094e8a",
		"dropsCategoryId": "CATEGORY_CHZZK",
        "dropsCategoryName": "์น˜์ง€์ง",
        "dropsClaimDate": "2024-08-01T01:58:33Z"
      }
    }
  },
  "subscription": {
    "clientId": "${clientId}",
    "status": "ENABLED",
    "method": {
      "methodType": "WEBHOOK",
      "url": "${Webhook URL}"
    }
  }
}

Notification Message fields

Name
Type
Description

message

Message

์ด๋ฒคํŠธ ์ •๋ณด ๋ฐ ๋‚ด์šฉ์„ ํฌํ•จ

subscription

Subscription

์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹  ๋ฐ›๋Š” client ์˜ metadata

Message

Name
Type
Description

messageId

String

๋ฉ”์‹œ์ง€ ์‹๋ณ„์ž id

version

String

message ํ˜•์‹ ๋ฒ„์ „ Chzzk-Event-Message-Version ํ—ค๋” ๊ฐ’๊ณผ ๋™์ผ

event

Event

์ด๋ฒคํŠธ ์ •๋ณด ์ด๋ฒคํŠธ ํƒ€์ž…์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Œ

Event

Name
Type
Description

eventTimeMillis

long

์ด๋ฒคํŠธ ๋ฐœ์ƒ์‹œ๊ฐ ๋ฐ€๋ฆฌ ์ดˆ ๋‹จ์œ„ (UTC)

version

String

Event data ํ˜•์‹ ๋ฒ„์ „ Chzzk-Event-Message-Data-Version ํ—ค๋” ๊ฐ’๊ณผ ๋™์ผ

eventType

String

์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ์œ ํ˜• (์ฐธ์กฐ : Event Types)

data

Data

์ด๋ฒคํŠธ ์ •๋ณด ์ด๋ฒคํŠธ ํƒ€์ž…์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Œ (์ฐธ์กฐ : Event Types)

Subscription

Name
Type
Description

clientId

String

clientId

status

String

ํ˜„์žฌ ๊ตฌ๋…์ƒํƒœ (DISABLED ๊ฒฝ์šฐ์—” ์ด๋ฒคํŠธ๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ENABLED๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ „๋‹ฌ๋จ)

method

Method

์ด๋ฒคํŠธ ์ „์†ก ๋ฐฉ๋ฒ•

Method

Name
Type
Description

methodType

String

Notification type์˜ ๊ฒฝ์šฐ ํ˜„์žฌ WEBHOOK ๊ฐ’์œผ๋กœ๋งŒ ์ „๋‹ฌ๋จ

url

String

Webhook URL

1.3 Event Types

Name
Type
Version
Description

Drop Reward Claim

drop_reward_claim

1

๋“œ๋กญ์Šค ๋ฆฌ์›Œ๋“œ ์•„์ดํ…œ ์‹ ์ฒญ ์ด๋ฒคํŠธ

1.3.1 drops_reward_claim

eventType์ด drop_reward_claim์œผ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.

Event Data

Name
Type
Description

dropsClaimId

String

์ง€๊ธ‰ ์‹ ์ฒญ ๋ฐ์ดํ„ฐ์˜ ๊ณ ์œ ํ•œ ์‹๋ณ„์ž ID

channelId

String

์น˜์ง€์ง ํ”„๋กœํ•„์„ ์‚ฌ์šฉํ•˜๋Š” ์œ ์ €์˜ ๊ณ ์œ ํ•œ ์‹๋ณ„์ž ID

dropsRewardId

String

์‹ ์ฒญํ•œ ๋ฆฌ์›Œ๋“œ ์•„์ดํ…œ ID

dropsCampaignId

String

์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋Š” ์บ ํŽ˜์ธ ID

dropsCategoryId

String

์บ ํŽ˜์ธ์— ํ• ๋‹น๋œ ์นดํ…Œ๊ณ ๋ฆฌ ID

dropsCategoryName

String

์บ ํŽ˜์ธ์— ํ• ๋‹น๋œ ๋…ธ์ถœ ๊ฐ€๋Šฅํ•œ ์นดํ…Œ๊ณ ๋ฆฌ ์ด๋ฆ„

dropsClaimDate

String

์ง€๊ธ‰ ์‹ ์ฒญํ•œ ๋‚ ์งœ UTC datetime(RFC 3339)

1.4 Event Message Verification

  • ์ด๋ฒคํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์ „์— ์•Œ๋ฆผ์„ ์ˆ˜์‹  ๋ฐ›๋Š” ๊ณณ์—์„œ ๋ฐ˜๋“œ์‹œ ์น˜์ง€์ง์—์„œ ๋ณด๋‚ธ ๋ฉ”์‹œ์ง€์ธ์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  • ์น˜์ง€์ง ๊ฐœ๋ฐœ์ž ์„ผํ„ฐ > ์„œ๋น„์Šค client ๋“ฑ๋ก ์‹œ ๋ฐœ๊ธ‰๋˜๋Š” client ์˜ secret key๋ฅผ ๋น„๋ฐ€ํ‚ค๋กœ ์‚ฌ์šฉํ•˜์—ฌ HMAC-SHA256 ์„œ๋ช…์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

  • ์•„๋ž˜์˜ ์ฝ”๋“œ snippet์„ ํ™•์ธํ•˜์—ฌ ๊ฒ€์ฆ ๊ตฌํ˜„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

    • Javascript ์—์„œ๋Š” ํ—ค๋”๋ฅผ ์†Œ๋ฌธ์ž๋กœ ํ‘œ๊ธฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    • Signature ๊ฐ€ ๋งค์นญ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, 2xx ์ƒํƒœ์ฝ”๋“œ๊ฐ€ ์•„๋‹Œ 4xx ์ƒํƒœ์ฝ”๋“œ ๊ฐ’์œผ๋กœ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค..

Javascript

const crypto = require('crypto');
const express = require('express');
const app = express();

// Notification request headers
const CHZZK_MESSAGE_ID = 'CHZZK-EVENT-MESSAGE-ID'.toLowerCase();
const CHZZK_MESSAGE_TIMESTAMP = 'CHZZK-EVENT-MESSAGE-TIMESTAMP'.toLowerCase();
const CHZZK_MESSAGE_SIGNATURE = 'CHZZK-EVENT-MESSAGE-SIGNATURE'.toLowerCase();
const CHZZK_MESSAGE_TYPE = 'CHZZK-EVENT-MESSAGE-TYPE'.toLowerCase();
const CHZZK_MESSAGE_TYPE_NOTIFICATION = 'NOTIFICATION'.toLowerCase();

const HMAC_PREFIX = 'sha256=';

......

    let secret = getSecret();
    let message = getHmacMessage(req);
    let hmac = HMAC_PREFIX + getHmac(secret, message);

    if (true === verifyMessage(hmac, req.headers[CHZZK_MESSAGE_SIGNATURE])) {

        let notification = JSON.parse(req.body);

        if (CHZZK_MESSAGE_TYPE_NOTIFICATION === req.headers[CHZZK_MESSAGE_TYPE]) {
            console.log(JSON.stringify(notification));
            res.sendStatus(200);
        }
    } else {
        console.log('403');
        res.sendStatus(403);
    }
});

function getSecret() {
    // client ๋“ฑ๋ก ์‹œ ๋ฐœ๊ธ‰๋˜๋Š” secret key๋ฅผ ๋น„๋ฐ€ํ‚ค๋กœ ์‚ฌ์šฉ (์•„๋ž˜๋Š” ์˜ˆ์‹œ ์‹ค์ œ ์‹œํฌ๋ฆฟํ‚ค ์•„๋‹˜)
    return "${secretKey}";
}


function getHmacMessage(request) {
    return (request.headers[CHZZK_MESSAGE_ID] +
        request.headers[CHZZK_MESSAGE_TIMESTAMP] +
        request.body);
}


function getHmac(secret, message) {
    return crypto.createHmac('sha256', secret)
        .update(message)
        .digest('hex');
}


function verifyMessage(hmac, verifySignature) {
    return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(verifySignature));
}

Java

public class WebhookNotificationSubscriptionTest {

    private static final String CHZZK_MESSAGE_ID = "Chzzk-Event-Message-Id";
    private static final String CHZZK_MESSAGE_TIMESTAMP = "Chzzk-Event-Message-Timestamp";
    private static final String CHZZK_MESSAGE_SIGNATURE = "Chzzk-Event-Message-Signature";
    private static final String CHZZK_MESSAGE_TYPE = "Chzzk-Event-Message-Type";
    private static final String CHZZK_MESSAGE_TYPE_NOTIFICATION = "notification";
    private static final String HMAC_PREFIX = "sha256=";
    private static final String HMAC_ALGORITHM = "HmacSHA256";

    public void handleEvent(String body, HttpServletRequest httpServletRequest) throws NoSuchAlgorithmException, InvalidKeyException {

        String secret = getSecret();
        String message = getHmacMessage(httpServletRequest, body);
        String hmac = HMAC_PREFIX + generateHmac(secret, message);

        if (verifyMessage(hmac, httpServletRequest.getHeader(CHZZK_MESSAGE_SIGNATURE))) {
            if (CHZZK_MESSAGE_TYPE_NOTIFICATION.equals(httpServletRequest.getHeader(CHZZK_MESSAGE_TYPE))) {
                System.out.println("success: " + body);
                                // Body Json Parsing
            }
        } else {
            // ๊ฒ€์ฆ๋˜์ง€ ์•Š์€ ๋ฉ”์‹œ์ง€ ํ™•์ธ
        }
    }

    private String getSecret() {
        return "${secretKey}";
    }

    private String getHmacMessage(HttpServletRequest httpServletRequest, String body) {
        return httpServletRequest.getHeader(CHZZK_MESSAGE_ID) +
            httpServletRequest.getHeader(CHZZK_MESSAGE_TIMESTAMP) +
            body;
    }


    private String generateHmac(String secret, String message) throws NoSuchAlgorithmException, InvalidKeyException {
        Mac mac = Mac.getInstance(HMAC_ALGORITHM);
        SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
        mac.init(secretKeySpec);
        byte[] hmacBytes = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hmacBytes);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    private boolean verifyMessage(String hmac, String verifySignature) {
        return hmac.equals(verifySignature);
    }
}

1.4.1 Retry Policy

์ด๋ฒคํŠธ ๋ฐœ์†ก์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๊ฒฝ์šฐ(์‘๋‹ต ์†๋„๊ฐ€ ๋А๋ฆฌ๊ฑฐ๋‚˜, 5xx์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ) notification message ์žฌ๋ฐœ์†ก์ด ์ •์ƒ์ ์œผ๋กœ ์ „์†ก๋  ๋•Œ๊นŒ์ง€ ์ตœ๋Œ€ 3ํšŒ ๋ฐœ์†กํ•ฉ๋‹ˆ๋‹ค.

Chzzk-Event-Message-Retry ํ—ค๋”๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์žฌ๋ฐœ์†ก์— ๋Œ€ํ•œ ์ด๋ฒคํŠธ ํ™•์ธ ๋ฐ ์žฌ๋ฐœ์†ก์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ(์ค‘๋ณต ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ) ๊ตฌํ˜„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. *์ด๋ฒคํŠธ ์žฌ๋ฐœ์†ก์˜ eventTimeMillis(์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ๊ฐ)๋Š” ๋ฉ”์‹œ์ง€ ์ „์†ก์‹œ๊ฐ์ด ์•„๋‹Œ ์žฌ๋ฐœ์†ก ์ „ ์ด๋ฒคํŠธ ์ „์†ก ์‹œ์ž‘ ์‹œ๊ฐ„์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

Previous๋“œ๋กญ์Šค ์—ฐ๋™ ๋ฐ ์ ˆ์ฐจNext๋“œ๋กญ์Šค ์บ ํŽ˜์ธ ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ ๊ฐ€์ด๋“œ

Last updated 5 months ago