Files
2nd/10_Wiki/Topics/Backend/WebHooks_and_Notifications.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
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>
2026-05-20 23:52:15 +09:00

5.7 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-webhooks-and-notifications WebHooks and Notifications 10_Wiki/Topics verified self
Webhooks
Event Callbacks
Push Notifications
none A 0.9 applied
webhooks
event-driven
http
async
integration
2026-05-10 pending
language framework
TypeScript / Python 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)

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)

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

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

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)

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)

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

🤖 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 패턴