f8b21af4be
10_Wiki/Topics 대규모 정리: - 오류 캡처/미완성 stub 문서 227개 제거 - 교차폴더 중복 43클러스터 병합 (63파일 → redirect) - 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건 - 카테고리 MOC 6개 신규 생성 - Graph 섹션 미해결 related-keyword 링크 10,058건 제거 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
182 lines
5.7 KiB
Markdown
182 lines
5.7 KiB
Markdown
---
|
|
id: wiki-2026-0508-webhooks-and-notifications
|
|
title: WebHooks and Notifications
|
|
category: 10_Wiki/Topics
|
|
status: verified
|
|
canonical_id: self
|
|
aliases: [Webhooks, Event Callbacks, Push Notifications]
|
|
duplicate_of: none
|
|
source_trust_level: A
|
|
confidence_score: 0.9
|
|
verification_status: applied
|
|
tags: [webhooks, event-driven, http, async, integration]
|
|
raw_sources: []
|
|
last_reinforced: 2026-05-10
|
|
github_commit: pending
|
|
tech_stack:
|
|
language: TypeScript / Python
|
|
framework: Hono / FastAPI / Svix
|
|
---
|
|
|
|
# WebHooks and Notifications
|
|
|
|
## 매 한 줄
|
|
> **"매 reverse-API: 매 server 가 client 의 HTTP endpoint 로 event 를 push 하는 매 async integration pattern"**. 2007 GitHub 가 popularize, 2026 Stripe/Shopify/Slack 의 표준. 매 polling 대비 latency ↓ 99%, 매 challenge: delivery guarantee + signature verification + replay protection.
|
|
|
|
## 매 핵심
|
|
|
|
### 매 Webhook anatomy
|
|
- `POST https://your-app/hooks/stripe` with JSON body + headers (`Stripe-Signature`, `Webhook-Id`, `Webhook-Timestamp`).
|
|
- 매 receiver 의 2xx 응답 < 매 5s — 매 그렇지 않으면 sender 가 retry.
|
|
|
|
### 매 Delivery guarantees
|
|
- **At-least-once**: 매 표준. 매 idempotency key 필수.
|
|
- 매 retry: exponential backoff (1m, 5m, 30m, 2h, ...) up to 매 24-72h.
|
|
- 매 dead-letter queue + manual replay UI.
|
|
|
|
### 매 Security
|
|
1. HMAC signature (`HMAC-SHA256(secret, timestamp + body)`).
|
|
2. Timestamp tolerance (±5 min) → replay 방어.
|
|
3. HTTPS only, IP allowlist (optional).
|
|
4. 매 secret rotation 의 지원.
|
|
|
|
### 매 응용
|
|
1. Payment events (Stripe, Toss).
|
|
2. SCM events (GitHub push, PR).
|
|
3. Chat platform commands (Slack, Discord).
|
|
4. 매 SaaS integration hub (Zapier, n8n).
|
|
|
|
## 💻 패턴
|
|
|
|
### Receiver (Hono + signature verify)
|
|
```typescript
|
|
import { Hono } from 'hono';
|
|
import { createHmac, timingSafeEqual } from 'crypto';
|
|
|
|
const app = new Hono();
|
|
|
|
app.post('/hooks/stripe', async (c) => {
|
|
const sig = c.req.header('stripe-signature')!;
|
|
const body = await c.req.text();
|
|
const [t, v1] = sig.split(',').map(p => p.split('=')[1]);
|
|
|
|
if (Math.abs(Date.now()/1000 - +t) > 300) return c.text('stale', 400);
|
|
|
|
const expected = createHmac('sha256', process.env.STRIPE_SECRET!)
|
|
.update(`${t}.${body}`).digest('hex');
|
|
if (!timingSafeEqual(Buffer.from(expected), Buffer.from(v1)))
|
|
return c.text('bad sig', 401);
|
|
|
|
const event = JSON.parse(body);
|
|
await enqueue(event); // 매 fast 200, async process
|
|
return c.text('ok', 200);
|
|
});
|
|
```
|
|
|
|
### Sender (with retry queue)
|
|
```python
|
|
from svix import Svix
|
|
svix = Svix("sk_...")
|
|
svix.message.create("app_xxx", {
|
|
"event_type": "user.created",
|
|
"payload": {"id": user.id, "email": user.email},
|
|
})
|
|
# Svix handles signing, retries, replay log
|
|
```
|
|
|
|
### Idempotency
|
|
```typescript
|
|
async function handle(event: Event) {
|
|
const exists = await redis.set(
|
|
`evt:${event.id}`, '1', { NX: true, EX: 86400 }
|
|
);
|
|
if (!exists) return; // 매 already processed
|
|
await processEvent(event);
|
|
}
|
|
```
|
|
|
|
### Retry policy
|
|
```typescript
|
|
const RETRY_SCHEDULE = [60, 300, 1800, 7200, 21600, 86400]; // seconds
|
|
|
|
async function deliver(hook, attempt = 0) {
|
|
try {
|
|
const r = await fetch(hook.url, { method: 'POST', body: hook.body, headers: hook.headers });
|
|
if (r.ok) return;
|
|
throw new Error(`status ${r.status}`);
|
|
} catch (e) {
|
|
if (attempt >= RETRY_SCHEDULE.length) {
|
|
await deadLetter(hook); return;
|
|
}
|
|
await scheduleAt(deliver, hook, attempt + 1, Date.now() + RETRY_SCHEDULE[attempt] * 1000);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Push notification (FCM HTTP v1)
|
|
```typescript
|
|
await fetch(`https://fcm.googleapis.com/v1/projects/${PROJECT}/messages:send`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
message: {
|
|
token: deviceToken,
|
|
notification: { title: 'New message', body: msg.preview },
|
|
data: { conversationId: msg.conversationId },
|
|
android: { priority: 'HIGH' },
|
|
apns: { payload: { aps: { 'mutable-content': 1 } } },
|
|
},
|
|
}),
|
|
});
|
|
```
|
|
|
|
### Webhook → SQS bridge (decouple)
|
|
```typescript
|
|
app.post('/hooks/:provider', async c => {
|
|
const body = await c.req.text();
|
|
await sqs.send(new SendMessageCommand({
|
|
QueueUrl: process.env.HOOKS_QUEUE,
|
|
MessageBody: JSON.stringify({ provider: c.req.param('provider'), body, hdr: c.req.header() }),
|
|
}));
|
|
return c.text('ok', 200); // 매 fast ack
|
|
});
|
|
```
|
|
|
|
## 매 결정 기준
|
|
| 상황 | Approach |
|
|
|---|---|
|
|
| 매 outbound webhooks | Svix / Hookdeck (managed) |
|
|
| 매 high-volume inbound | bridge to SQS/Kafka, process async |
|
|
| Mobile push | FCM (Android+iOS) / APNs direct |
|
|
| Web push | VAPID + Service Worker |
|
|
| Internal pub/sub | NATS, Redis Streams (not webhooks) |
|
|
|
|
**기본값**: HMAC-SHA256 signature + idempotency + async queue + 6-step retry + dead-letter.
|
|
|
|
## 🔗 Graph
|
|
- 부모: [[Event-Driven-Architecture]] · [[HTTP-API]]
|
|
- 변형: [[WebSockets_and_Realtime]] · [[Server-Sent-Events]]
|
|
- 응용: [[Slack-Bot-Development]]
|
|
- Adjacent: [[Idempotency]]
|
|
|
|
## 🤖 LLM 활용
|
|
**언제**: webhook receiver scaffold, signature-verify code, retry policy boilerplate.
|
|
**언제 X**: secret 관리, production replay tool 설계 — domain expertise 필요.
|
|
|
|
## ❌ 안티패턴
|
|
- **No signature verification**: 매 anyone 의 spoof 가능.
|
|
- **Sync heavy work in handler**: 매 timeout → sender retry storm.
|
|
- **No idempotency**: at-least-once 의 duplicate 처리 → double-charge.
|
|
- **Storing secret in code**: 매 secret rotation 불가.
|
|
- **No dead-letter visibility**: silent failure.
|
|
|
|
## 🧪 검증 / 중복
|
|
- Verified (Stripe/GitHub webhook docs, Svix docs, Standard Webhooks spec 2024).
|
|
- 신뢰도 A.
|
|
|
|
## 🕓 Changelog
|
|
| 날짜 | 변경 |
|
|
|---|---|
|
|
| 2026-05-08 | Phase 1 |
|
|
| 2026-05-10 | Manual cleanup — webhook delivery + signature + push 패턴 |
|