
์ธ์ด ๐ฐ๐ท ํ๊ตญ์ด
๊ฐ์
๊ตฌํ
๋ด๋ถ ๋์
FastComments๋ฅผ ์ฌ์ฉํ๋ฉด ๋๊ธ์ด ์์คํ ์ ์ถ๊ฐ๋๊ฑฐ๋, ์ ๋ฐ์ดํธ๋๊ฑฐ๋, ์ ๊ฑฐ๋ ๋๋ง๋ค API ์๋ํฌ์ธํธ๋ฅผ ํธ์ถํ ์ ์์ต๋๋ค.
์ฐ๋ฆฌ๋ ์ด๋ฅผ HTTP/HTTPS๋ฅผ ํตํ ๋น๋๊ธฐ ์นํํฌ๋ก ๊ตฌํํฉ๋๋ค.
์นํํฌ๋ ๋ฌด์์ธ๊ฐ 
์นํ ์ ๋ ์์คํ ๊ฐ์ ๋ฉ์ปค๋์ฆ ๋๋ ํตํฉ์ผ๋ก, "์์ฐ์" (FastComments)๊ฐ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๊ณ "์๋น์" (์ฌ์ฉ์)๊ฐ API ํธ์ถ์ ํตํด ํด๋น ์ด๋ฒคํธ๋ฅผ ์๋นํฉ๋๋ค.
์ง์๋๋ ์ด๋ฒคํธ ๋ฐ ๋ฆฌ์์ค 
FastComments๋ Comment ๋ฆฌ์์ค์ ๋ํด์๋ง ์นํ ์ ์ง์ํฉ๋๋ค.
๋๊ธ ์์ฑ, ์ญ์ ๋ฐ ์ ๋ฐ์ดํธ์ ๋ํ ์นํ ์ ์ง์ํฉ๋๋ค.
์ด๋ค ๊ฐ๊ฐ์ ์์คํ ์์ ๋ณ๊ฐ์ ์ด๋ฒคํธ๋ก ๊ฐ์ฃผ๋๋ฉฐ, ๋ฐ๋ผ์ ์นํ ์ด๋ฒคํธ์ ๋ํด ์๋ก ๋ค๋ฅธ ์๋ฏธ ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๋๋ค.
๋ก์ปฌ ๊ฐ๋ฐ ์ค์ 
๋ก์ปฌ ๊ฐ๋ฐ์ ๊ฒฝ์ฐ ngrok์ ๊ฐ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ธ์.
์์คํ ๋ณด์์ ๋จ์ํํ๊ธฐ ์ํด ๋ก์ปฌ ๊ฐ๋ฐ์ ๋ค๋ฅธ ํ๊ฒฝ์ ์ค์ ํ๊ณ ๋ณด์์ ์ ์งํ๋ ๊ฒ๊ณผ ๋์ผํ ์ ์ฐจ๋ฅผ ๋ฐ๋ฆ ๋๋ค.
Step 1: Add "localhost" to domains in your account.
"localhost"๋ฅผ ์ฌ๊ธฐ์ ๋๋ฉ์ธ์ผ๋ก ์ถ๊ฐํ์ธ์.
Step 2: Pick an API Key
๋๋ฉ์ธ์ ๋ํ ์นํ ๊ตฌ์ฑ์ ์ถ๊ฐํ ์์ ์ด๋ฏ๋ก API ํค๊ฐ ํ์ํฉ๋๋ค. ์ฌ๊ธฐ์์ ํ ์ ์์ต๋๋ค.
Under "Associate with domain" - select your "localhost" domain.
NOTE: Alternatively, you can use one API Secret for all testing activity and staging environments. Simply add an API Secret for "All Domains", and give it a name like "test".
์ด์ ๋๋ฉ์ธ์ ๋ํด API Secret์ด ์ ์๋์ด ์๋์ง ํ์ธํ์ธ์. ๋ค๋ฅธ ๋ชจ๋ ๋๋ฉ์ธ์ ์ด๋ฒคํธ๋ ์์ผ๋์นด๋(ํ ์คํธ) ์ํฌ๋ฆฟ์ ์ฌ์ฉํฉ๋๋ค.
Step 3: Add Your Webhook
ngrok ๋๋ ์ ์ฌํ ๋๊ตฌ๋ฅผ ์คํํ๋ ๋์ "localhost"์ ๊ฐ์ ์ฌ๊ธฐ์์ ์ค์ ํ์ธ์.
When clicking Send Test Payload, we will send two test events to check that you validate the API key.
Once it validates, hit Save.
Step 4: Add A Comment
์ด์ ๋๊ธ์ ์ถ๊ฐ, ํธ์ง ๋๋ ์ญ์ ํ ์ ์์ผ๋ฉฐ ํ ์คํธ์ฉ API ํค๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฒคํธ๊ฐ ๋ก์ปฌ ๊ฐ๋ฐ ๋จธ์ ์ผ๋ก ์ ๋ฌ๋๋ ๊ฒ์ ๋ณผ ์ ์์ด์ผ ํฉ๋๋ค. ์ต๋ 30์ด์ ์ง์ฐ ์ด๋ฒคํธ๊ฐ ๋จธ์ ์ ๋๋ฌํ๋ ๋ฐ ์์ ์ ์์ต๋๋ค.
์ค์ 
localhost์ ๋ํด์๋ ํ๋ก๋์
์์ ํ ๊ฒ๊ณผ ๋์ผํ ๋จ๊ณ๋ฅผ ๋ฐ๋ฅด์ธ์. ํ๋ก๋์
๋๋ฉ์ธ๊ณผ API Secrets๊ฐ ์ค์ ๋์ด ์๋์ง ํ์ธํ์ธ์.
๋จผ์ ์นํํฌ ๊ด๋ฆฌ์๋ก ์ด๋ํ์ธ์. ์ด๋ ๋ฐ์ดํฐ ๊ด๋ฆฌ -> ์นํํฌ์์ ์ ๊ทผํ ์ ์์ต๋๋ค.
๊ตฌ์ฑ ํ์ด์ง๋ ๋ค์๊ณผ ๊ฐ์ด ํ์๋ฉ๋๋ค:
์ด ํ์ด์ง์์ ๊ฐ ๋๊ธ ์ด๋ฒคํธ ์ ํ์ ๋ํ ์๋ํฌ์ธํธ๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค.
๊ฐ ์ด๋ฒคํธ ์ ํ๋ง๋ค ํตํฉ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ๋์๋์ง ํ์ธํ๋ ค๋ฉด ๋ฐ๋์ 'Send Test Payload'๋ฅผ ํด๋ฆญํ์ธ์. ์์ธํ ๋ด์ฉ์ ๋ค์ ์น์ "ํ ์คํธ"๋ฅผ ์ฐธ์กฐํ์ธ์.
ํ
์คํธ 
Webhooks ๊ด๋ฆฌ์์๋ ๊ฐ ์ด๋ฒคํธ ์ ํ (Create, Update, Delete)์ ๋ํด Send Test Payload ๋ฒํผ์ด ์์ต๋๋ค. Create ๋ฐ Update ์ด๋ฒคํธ๋ ๋๋ฏธ WebhookComment ๊ฐ์ฒด๋ฅผ ์ ์กํ๋ฉฐ, Delete ํ
์คํธ๋ ID๋ง ์๋ ๋๋ฏธ ์์ฒญ ๋ณธ๋ฌธ์ ์ ์กํฉ๋๋ค.
ํ์ด๋ก๋ ๊ฒ์ฆ
์นํ ํตํฉ์ ํ ์คํธํ ๋, ๋ค์ด์ค๋ ์์ฒญ์ ๋ค์ ํค๋๋ค์ด ํฌํจ๋์ด ์๋์ง ํ์ธํ์ธ์:
token- ๊ทํ์ API ๋น๋ฐX-FastComments-Timestamp- Unix ํ์์คํฌํ(์ด)X-FastComments-Signature- HMAC-SHA256 ์๋ช
HMAC ์๋ช ๊ฒ์ฆ์ ์ฌ์ฉํ์ฌ ํ์ด๋ก๋์ ์ง์๋ฅผ ํ์ธํ์ธ์.
ํ ์คํธ ๋๊ตฌ
๊ฐ๋ฐ ์ค ๋ค์ด์ค๋ ์นํ ํ์ด๋ก๋๋ฅผ ๊ฒ์ฌํ๊ธฐ ์ํด webhook.site ๋๋ ngrok๊ณผ ๊ฐ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ด๋ฒคํธ ์ ํ
- Create Event: ์ ๋๊ธ์ด ์์ฑ๋ ๋ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค. ๊ธฐ๋ณธ ๋ฉ์๋: PUT
- Update Event: ๋๊ธ์ด ํธ์ง๋ ๋ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค. ๊ธฐ๋ณธ ๋ฉ์๋: PUT
- Delete Event: ๋๊ธ์ด ์ญ์ ๋ ๋ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค. ๊ธฐ๋ณธ ๋ฉ์๋: DELETE
๊ฐ ์ด๋ฒคํธ๋ ์์ฒญ ๋ณธ๋ฌธ์ ์ ์ฒด ๋๊ธ ๋ฐ์ดํฐ๋ฅผ ํฌํจํฉ๋๋ค (ํ์ด๋ก๋ ํ์์ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ฐธ์กฐ).
๋ฐ์ดํฐ ๊ตฌ์กฐ 
์นํํฌ๋ก ์ ์ก๋๋ ์ ์ผํ ๊ตฌ์กฐ๋ ์๋ TypeScript์ ์ค๋ช ๋ WebhookComment ๊ฐ์ฒด์ ๋๋ค.
WebhookComment ๊ฐ์ฒด ๊ตฌ์กฐ
"Create" ์ด๋ฒคํธ ๊ตฌ์กฐ
"create" ์ด๋ฒคํธ ์์ฒญ ๋ณธ๋ฌธ์ WebhookComment ๊ฐ์ฒด์ ๋๋ค.
"Update" ์ด๋ฒคํธ ๊ตฌ์กฐ
"update" ์ด๋ฒคํธ ์์ฒญ ๋ณธ๋ฌธ์ WebhookComment ๊ฐ์ฒด์ ๋๋ค.
"Delete" ์ด๋ฒคํธ ๊ตฌ์กฐ
"delete" ์ด๋ฒคํธ ์์ฒญ ๋ณธ๋ฌธ์ WebhookComment ๊ฐ์ฒด์ ๋๋ค.
๋ณ๊ฒฝ โ 2023๋
11์ 14์ผ ๊ธฐ์ค
์ด์ ์๋ "delete" ์ด๋ฒคํธ ์์ฒญ ๋ณธ๋ฌธ์ ๋๊ธ id๋ง ํฌํจ๋์ด ์์์ต๋๋ค. ์ด์ ์ญ์ ์์ ์ ์ ์ฒด ๋๊ธ์ ํฌํจํฉ๋๋ค.
Run 
When users are tagged in a comment, the information is stored in a list called mentions. Each object in that list
has the following structure.
Run 
HTTP ๋ฉ์๋
๊ด๋ฆฌ์ ํจ๋์์ ๊ฐ ์นํํฌ ์ด๋ฒคํธ ์ ํ์ ๋ํ HTTP ๋ฉ์๋๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค:
- Create Event: POST ๋๋ PUT (๊ธฐ๋ณธ๊ฐ: PUT)
- Update Event: POST ๋๋ PUT (๊ธฐ๋ณธ๊ฐ: PUT)
- Delete Event: DELETE, POST ๋๋ PUT (๊ธฐ๋ณธ๊ฐ: DELETE)
๋ชจ๋ ์์ฒญ์ ID๊ฐ ํฌํจ๋์ด ์์ผ๋ฏ๋ก Create ๋ฐ Update ์์ ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฉฑ๋ฑํฉ๋๋ค (PUT). ๋์ผํ Create ๋๋ Update ์์ฒญ์ ๋ฐ๋ณตํด๋ ๊ท์ธก์์ ์ค๋ณต ๊ฐ์ฒด๊ฐ ์์ฑ๋์ง ์์์ผ ํฉ๋๋ค.
์์ฒญ ํค๋
๊ฐ ์นํํฌ ์์ฒญ์๋ ๋ค์ ํค๋๊ฐ ํฌํจ๋ฉ๋๋ค:
| Header | Description |
|---|---|
Content-Type |
application/json |
token |
๊ทํ์ API ์ํฌ๋ฆฟ |
X-FastComments-Timestamp |
์์ฒญ์ด ์๋ช ๋ ์์ ์ Unix ํ์์คํฌํ(์ด) |
X-FastComments-Signature |
HMAC-SHA256 ์๋ช
(sha256=<hex>) |
HMAC ์๋ช ๊ฒ์ฆ์ ๋ํ ์ ๋ณด๋ ๋ณด์ ๋ฐ API ํ ํฐ์์ ํ์ธํ์ธ์.
๋ณด์ ๋ฐ API ํ ํฐ 
FastComments ์นํ ์์ฒญ์๋ ๋ณด์์ ์ํด ์ฌ๋ฌ ์ธ์ฆ ๋ฉ์ปค๋์ฆ์ด ํฌํจ๋์ด ์์ต๋๋ค.
์ ์ก๋๋ ํค๋
| ํค๋ | ์ค๋ช |
|---|---|
token |
๊ทํ์ API Secret(ํ์ ํธํ์ฑ์ฉ) |
X-FastComments-Timestamp |
์์ฒญ์ด ์๋ช ๋ ๋์ Unix ํ์์คํฌํ(์ด) |
X-FastComments-Signature |
ํ์ด๋ก๋์ HMAC-SHA256 ์๋ช |
HMAC ์๋ช ๊ฒ์ฆ (๊ถ์ฅ)
์นํ ํ์ด๋ก๋๊ฐ ์ง๋ณธ์ด๋ฉฐ ๋ณ์กฐ๋์ง ์์๋์ง ํ์ธํ๊ธฐ ์ํด HMAC ์๋ช ๊ฒ์ฆ์ ๊ฐ๋ ฅํ ๊ถ์ฅํฉ๋๋ค.
์๋ช
ํ์: sha256=<hex-encoded-signature>
์๋ช ์ด ๊ณ์ฐ๋๋ ๋ฐฉ๋ฒ:
- ๊ฒฐํฉ:
timestamp + "." + JSON_payload_body - API Secret์ ํค๋ก ์ฌ์ฉํ์ฌ HMAC-SHA256์ ๊ณ์ฐ
- ๊ฒฐ๊ณผ๋ฅผ 16์ง์๋ก ์ธ์ฝ๋ฉ
์์ ๊ฒ์ฆ (Node.js)
const crypto = require('crypto');
function verifyWebhookSignature(req, apiSecret) {
const timestamp = req.headers['x-fastcomments-timestamp'];
const signature = req.headers['x-fastcomments-signature'];
if (!timestamp || !signature) {
return false;
}
// ํ์์คํฌํ๊ฐ ์ต๊ทผ์ธ์ง ํ์ธํฉ๋๋ค (5๋ถ ์ด๋ด)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
return false; // ์ฌ์ ๊ณต๊ฒฉ ๋ฐฉ์ง
}
// ์๋ช
๊ฒ์ฆ
const payload = JSON.stringify(req.body);
const expectedSignature = crypto
.createHmac('sha256', apiSecret)
.update(`${timestamp}.${payload}`)
.digest('hex');
return signature === `sha256=${expectedSignature}`;
}
์์ ๊ฒ์ฆ (Python)
import hmac
import hashlib
import time
import json
def verify_webhook_signature(headers, body, api_secret):
timestamp = headers.get('X-FastComments-Timestamp')
signature = headers.get('X-FastComments-Signature')
if not timestamp or not signature:
return False
# ํ์์คํฌํ๊ฐ ์ต์ ์ธ์ง ํ์ธํฉ๋๋ค
now = int(time.time())
if abs(now - int(timestamp)) > 300:
return False
# ์๋ช
๊ฒ์ฆ
payload = json.dumps(body, separators=(',', ':'))
message = f"{timestamp}.{payload}"
expected = hmac.new(
api_secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
return signature == f"sha256={expected}"
์์ ๊ฒ์ฆ (PHP)
function verifyWebhookSignature($headers, $body, $apiSecret) {
$timestamp = $headers['X-FastComments-Timestamp'] ?? null;
$signature = $headers['X-FastComments-Signature'] ?? null;
if (!$timestamp || !$signature) {
return false;
}
// ํ์์คํฌํ๊ฐ ์ต์ ์ธ์ง ํ์ธํฉ๋๋ค (5๋ถ ์ด๋ด)
$now = time();
if (abs($now - intval($timestamp)) > 300) {
return false;
}
// ์๋ช
๊ฒ์ฆ
$payload = json_encode($body, JSON_UNESCAPED_SLASHES);
$message = $timestamp . '.' . $payload;
$expectedSignature = 'sha256=' . hash_hmac('sha256', $message, $apiSecret);
return hash_equals($expectedSignature, $signature);
}
๋ ๊ฑฐ์ ์ธ์ฆ
ํ์ ํธํ์ฑ์ ์ํด ๊ทํ์ API Secret์ด ํฌํจ๋ token ํค๋๋ ์ฌ์ ํ ์ ์ก๋ฉ๋๋ค. ๊ทธ๋ฌ๋ HMAC ๊ฒ์ฆ์ ์ฌ์ ๊ณต๊ฒฉ์ผ๋ก๋ถํฐ ๋ณดํธํ๋ฏ๋ก ๋ณด์ ํฅ์์ ์ํด HMAC ๊ฒ์ฆ์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์
ํ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
๋์ ๋ฐฉ์ ๋ฐ ์ฌ์๋ ์ฒ๋ฆฌ 
์์คํ ๋ด Comment ๊ฐ์ฒด์ ๋ํ ๋ชจ๋ ๋ณ๊ฒฝ์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๋ฉฐ ๊ทธ ์ด๋ฒคํธ๋ ํ์ ๋ค์ด๊ฐ๋๋ค.
์ด๊ธฐ ์นํ ์ด๋ฒคํธ๋ ๋ณดํต ์ด๋ฒคํธ ์์ค๊ฐ ๋ฐ์ํ ํ 6์ด ์ด๋ด์ ์ ์ก๋ฉ๋๋ค.
API๊ฐ ๋ค์ด๋๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด Webhooks ๊ด๋ฆฌ์์์ ์ด ํ๋ฅผ ๋ชจ๋ํฐ๋งํ ์ ์์ต๋๋ค.
๊ทํ์ API์ ๋ํ ์์ฒญ์ด ์คํจํ๋ฉด, ์ฐ๋ฆฌ๋ ๊ทธ๊ฒ์ ์ผ์ ์ ๋ฐ๋ผ ๋ค์ ํ์ ๋ฃ์ต๋๋ค.
That schedule is 1 Minute * the retry count. If the call fails once, it'll try again in
a minute. If it fails twice, it'll then wait two minutes, and so on. This is so that we
don't overload your API if you are going down to load related reasons.
์นํ ์ ๋ก๊ทธ ํ์ด์ง์์ ์ทจ์ํ ์ ์์ต๋๋ค.
๊ฒฐ๋ก
์ด๋ก์จ ์ฐ๋ฆฌ์ Webhooks ๋ฌธ์๊ฐ ๋ง๋ฌด๋ฆฌ๋ฉ๋๋ค.
FastComments Webhook ํตํฉ์ด ์ดํดํ๊ธฐ ์ฝ๊ณ ๋น ๋ฅด๊ฒ ์ค์ ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
๋ฌธ์์์ ๋๋ฝ๋ ๋ถ๋ถ์ ๋ฐ๊ฒฌํ์ จ๋ค๊ณ ์๊ฐ๋๋ฉด ์๋์ ์๋ ค์ฃผ์ธ์.