@k-msg/webhook 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README_ko.md ADDED
@@ -0,0 +1,176 @@
1
+ # @k-msg/webhook
2
+
3
+ HTTP 엔드포인트로 실시간 메시지 이벤트를 전달하기 위한 웹훅(Webhook) 전송 도구입니다.
4
+
5
+ 이 패키지는 아래 기능을 제공합니다.
6
+ - `WebhookService`: 편의용 Facade (in-memory 엔드포인트 레지스트리 + 배치 처리)
7
+ - `WebhookDispatcher`: HTTP 전송 + 재시도(백오프)
8
+ - `SecurityManager`: HMAC 서명 생성/검증
9
+ - Zod 스키마: `WebhookEventSchema`, `WebhookEndpointSchema`, `WebhookDeliverySchema`
10
+
11
+ 참고:
12
+ - 기본 `WebhookService` 저장소는 in-memory입니다. 영속 저장/고급 워크플로우가 필요하면 `EndpointManager`, `DeliveryStore` 같은 빌딩 블록을 활용하세요.
13
+
14
+ ## 설치
15
+
16
+ ```bash
17
+ npm install @k-msg/webhook
18
+ # 또는
19
+ bun add @k-msg/webhook
20
+ ```
21
+
22
+ ## 빠른 시작 (WebhookService)
23
+
24
+ ```ts
25
+ import {
26
+ WebhookEventType,
27
+ WebhookService,
28
+ type WebhookConfig,
29
+ } from "@k-msg/webhook";
30
+
31
+ const config: WebhookConfig = {
32
+ maxRetries: 3,
33
+ retryDelayMs: 1000,
34
+ // Optional: maxDelayMs, backoffMultiplier, jitter
35
+ timeoutMs: 30_000,
36
+ enableSecurity: true,
37
+ // Optional: 엔드포인트에 secret이 없을 때 fallback으로 사용됩니다.
38
+ secretKey: process.env.WEBHOOK_SECRET,
39
+ // Optional: algorithm, signatureHeader, signaturePrefix
40
+ enabledEvents: [
41
+ WebhookEventType.MESSAGE_SENT,
42
+ WebhookEventType.MESSAGE_DELIVERED,
43
+ WebhookEventType.MESSAGE_FAILED,
44
+ ],
45
+ batchSize: 10,
46
+ batchTimeoutMs: 5_000,
47
+ };
48
+
49
+ const service = new WebhookService(config);
50
+
51
+ const endpoint = await service.registerEndpoint({
52
+ url: "https://example.com/webhooks/k-msg",
53
+ active: true,
54
+ events: [WebhookEventType.MESSAGE_SENT, WebhookEventType.MESSAGE_FAILED],
55
+ // Optional: 엔드포인트별 secret (config.secretKey보다 우선)
56
+ secret: process.env.WEBHOOK_SECRET,
57
+ // Optional: 엔드포인트별 재시도 설정
58
+ retryConfig: { maxRetries: 5, retryDelayMs: 1000, backoffMultiplier: 2 },
59
+ // Optional: 메타데이터 기반 필터
60
+ filters: { providerId: ["iwinv", "solapi"] },
61
+ });
62
+
63
+ // 비동기 emit (배치 처리됨)
64
+ await service.emit({
65
+ id: crypto.randomUUID(),
66
+ type: WebhookEventType.MESSAGE_SENT,
67
+ timestamp: new Date(),
68
+ data: { messageId: "msg_123", status: "sent" },
69
+ metadata: { providerId: "iwinv", messageId: "msg_123" },
70
+ version: "1.0",
71
+ });
72
+
73
+ // 동기 emit (딜리버리 결과를 반환)
74
+ const deliveries = await service.emitSync({
75
+ id: crypto.randomUUID(),
76
+ type: WebhookEventType.MESSAGE_FAILED,
77
+ timestamp: new Date(),
78
+ data: { messageId: "msg_456", status: "failed" },
79
+ metadata: { providerId: "solapi", messageId: "msg_456" },
80
+ version: "1.0",
81
+ });
82
+
83
+ // 최근 딜리버리 조회(in-memory)
84
+ const recent = await service.getDeliveries(endpoint.id);
85
+ console.log(deliveries.length, recent.length);
86
+
87
+ await service.shutdown();
88
+ ```
89
+
90
+ ### 엔드포인트 등록 동작
91
+
92
+ `registerEndpoint()`는 URL 유효성 검증 후, `testEndpoint()`를 통해 테스트 웹훅(`system.maintenance`)을 1회 전송합니다.
93
+
94
+ ## 보안 (HMAC 서명)
95
+
96
+ 보안이 활성화되어 있고 secret이 존재하면(`endpoint.secret` 또는 `config.secretKey`), 아래 헤더가 포함됩니다.
97
+ - `X-Webhook-Timestamp`: unix epoch seconds (string)
98
+ - `X-Webhook-Signature`: HMAC 서명 (기본: `sha256=<hex>`)
99
+
100
+ 서명 입력 문자열은 아래 형식입니다.
101
+
102
+ ```
103
+ ${timestamp}.${rawBody}
104
+ ```
105
+
106
+ 수신 측에서는 반드시 "raw body 문자열" 그대로 검증해야 합니다.
107
+
108
+ ### Hono 예시
109
+
110
+ ```ts
111
+ import { Hono } from "hono";
112
+ import { SecurityManager } from "@k-msg/webhook";
113
+
114
+ const app = new Hono();
115
+ const security = new SecurityManager({
116
+ algorithm: "sha256",
117
+ signatureHeader: "X-Webhook-Signature",
118
+ signaturePrefix: "sha256=",
119
+ });
120
+
121
+ app.post("/webhooks/k-msg", async (c) => {
122
+ const payload = await c.req.text();
123
+
124
+ const signature = c.req.header("X-Webhook-Signature") ?? "";
125
+ const timestamp = c.req.header("X-Webhook-Timestamp") ?? "";
126
+ const secret = process.env.WEBHOOK_SECRET ?? "";
127
+
128
+ if (!security.verifyTimestamp(timestamp, 300)) {
129
+ return c.json({ error: "Request too old" }, 401);
130
+ }
131
+ if (!security.verifySignatureWithTimestamp(payload, timestamp, signature, secret)) {
132
+ return c.json({ error: "Invalid signature" }, 401);
133
+ }
134
+
135
+ const event = JSON.parse(payload);
136
+ return c.json({ ok: true, type: event.type });
137
+ });
138
+ ```
139
+
140
+ ## 재시도 및 상태
141
+
142
+ `WebhookDispatcher`는 실패한 전송을 지수 백오프로 재시도합니다.
143
+
144
+ 딜리버리 상태:
145
+ - `success`: 2xx 응답을 받음
146
+ - `failed`: 재시도 대상이 아닌 실패(주로 non-retryable 4xx)
147
+ - `exhausted`: 재시도 가능한 실패였지만, 재시도 횟수 소진
148
+
149
+ ## 필터링
150
+
151
+ 엔드포인트는 이벤트 메타데이터 기반으로 전달을 필터링할 수 있습니다.
152
+
153
+ ```ts
154
+ await service.registerEndpoint({
155
+ url: "https://example.com/webhooks/k-msg",
156
+ active: true,
157
+ events: [WebhookEventType.MESSAGE_SENT],
158
+ filters: {
159
+ providerId: ["iwinv"],
160
+ channelId: ["marketing"],
161
+ templateId: ["welcome-template"],
162
+ },
163
+ });
164
+ ```
165
+
166
+ ## Zod 스키마
167
+
168
+ 이 패키지는 검증용 Zod 스키마를 export 합니다.
169
+ - `WebhookEventSchema` (timestamp는 string/number 입력을 `Date`로 coerce)
170
+ - `WebhookEndpointSchema`
171
+ - `WebhookDeliverySchema`
172
+
173
+ ## License
174
+
175
+ MIT
176
+
package/dist/index.d.ts CHANGED
@@ -16,4 +16,4 @@ export { DefaultHttpClient, type HttpClient, MockHttpClient, WebhookDispatcher,
16
16
  export { WebhookRegistry } from "./services/webhook.registry";
17
17
  export { WebhookService } from "./services/webhook.service";
18
18
  export type { WebhookAttempt, WebhookBatch, WebhookConfig, WebhookDelivery, WebhookDeliveryData, WebhookEndpoint, WebhookEndpointData, WebhookEvent, WebhookEventData, WebhookSecurity, WebhookStats, WebhookTestResult, } from "./types/webhook.types";
19
- export { WebhookEventType } from "./types/webhook.types";
19
+ export { WebhookDeliverySchema, WebhookEndpointSchema, WebhookEventSchema, WebhookEventType, } from "./types/webhook.types";