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
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
message
Message
์ด๋ฒคํธ ์ ๋ณด ๋ฐ ๋ด์ฉ์ ํฌํจ
subscription
Subscription
์ด๋ฒคํธ๋ฅผ ์์ ๋ฐ๋ client ์ metadata
Message
messageId
String
๋ฉ์์ง ์๋ณ์ id
version
String
message ํ์ ๋ฒ์ Chzzk-Event-Message-Version ํค๋ ๊ฐ๊ณผ ๋์ผ
event
Event
์ด๋ฒคํธ ์ ๋ณด ์ด๋ฒคํธ ํ์ ์ ๋ฐ๋ผ ๋ฐ์ดํฐ๊ฐ ๋ฌ๋ผ์ง ์ ์์
Event
eventTimeMillis
long
์ด๋ฒคํธ ๋ฐ์์๊ฐ ๋ฐ๋ฆฌ ์ด ๋จ์ (UTC)
version
String
Event data ํ์ ๋ฒ์ Chzzk-Event-Message-Data-Version ํค๋ ๊ฐ๊ณผ ๋์ผ
eventType
String
์ด๋ฒคํธ ๋ฐ์ดํฐ ์ ํ (์ฐธ์กฐ : Event Types)
data
Data
์ด๋ฒคํธ ์ ๋ณด ์ด๋ฒคํธ ํ์ ์ ๋ฐ๋ผ ๋ฐ์ดํฐ๊ฐ ๋ฌ๋ผ์ง ์ ์์ (์ฐธ์กฐ : Event Types)
Subscription
clientId
String
clientId
status
String
ํ์ฌ ๊ตฌ๋ ์ํ (DISABLED ๊ฒฝ์ฐ์ ์ด๋ฒคํธ๊ฐ ์ ๋ฌ๋์ง ์์ผ๋ฏ๋ก ENABLED๊ฐ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ ๋ฌ๋จ)
method
Method
์ด๋ฒคํธ ์ ์ก ๋ฐฉ๋ฒ
Method
methodType
String
Notification type์ ๊ฒฝ์ฐ ํ์ฌ WEBHOOK ๊ฐ์ผ๋ก๋ง ์ ๋ฌ๋จ
url
String
Webhook URL
1.3 Event Types
Drop Reward Claim
drop_reward_claim
1
๋๋กญ์ค ๋ฆฌ์๋ ์์ดํ ์ ์ฒญ ์ด๋ฒคํธ
1.3.1 drops_reward_claim
eventType์ด drop_reward_claim์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค.
Event Data
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(์ด๋ฒคํธ ๋ฐ์ ์๊ฐ)๋ ๋ฉ์์ง ์ ์ก์๊ฐ์ด ์๋ ์ฌ๋ฐ์ก ์ ์ด๋ฒคํธ ์ ์ก ์์ ์๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ์ ์กํฉ๋๋ค.
Last updated