@zap-studio/webhooks 0.4.0 → 1.0.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/CHANGELOG.md +43 -28
- package/LICENSE +1 -1
- package/README.md +57 -142
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +16 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -2
- package/dist/router.d.ts +82 -25
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +103 -49
- package/dist/router.js.map +1 -1
- package/dist/types.d.ts +130 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/verify.d.ts +5 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +26 -6
- package/dist/verify.js.map +1 -1
- package/package.json +9 -10
- package/dist/utils.d.ts +0 -18
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -24
- package/dist/utils.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [1.0.0]
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
`WebhookRouter`'s stateless private static methods (`runBeforeHooks`, `runAfterHooks`, `createHandlerEntry`, `parseRequestBody`, `validatePayload`, `executeHandler`) are now module-level functions in `router.ts`. Internal-only change; the public API (`WebhookRouter`, `createWebhookRouter`, `.register()`, `.handle()`) is unaffected.
|
|
12
|
+
|
|
13
|
+
HMAC signature verification decodes the incoming header's hex signature to bytes and compares it against the computed digest byte-for-byte, instead of hex-encoding the digest and comparing hex text. Behavior is unchanged for valid requests; this only affects internals (fewer bytes compared, and the header's hex is no longer case-normalized as text since decoding handles case natively).
|
|
14
|
+
|
|
15
|
+
`constantTimeEquals` moved from `utils.ts` into `verify.ts` (its only consumer) and now compares `Uint8Array`s (bytes) instead of strings. Still exported from `@zap-studio/webhooks` and `@zap-studio/webhooks/verify`.
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
Removed the `./utils` subpath export.
|
|
20
|
+
|
|
21
|
+
## [0.4.0]
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
4
24
|
|
|
5
25
|
The custom `NormalizedRequest`/`NormalizedResponse` contract is gone. `router.handle` now takes a standard Web API `Request` and returns a standard `Response`, so the router plugs directly into fetch-native runtimes (Bun, Deno, Cloudflare Workers, Next.js route handlers, Hono) with no adapter layer.
|
|
6
26
|
|
|
7
|
-
Breaking changes
|
|
27
|
+
**Breaking changes:**
|
|
8
28
|
|
|
9
29
|
- `handle(req: NormalizedRequest): Promise<NormalizedResponse>` → `handle(request: Request): Promise<Response>`.
|
|
10
30
|
- Handlers receive `{ request, rawBody, path, payload }` (a `WebhookContext` plus the validated `payload`) and return a `Response` or `undefined` (default `200` `"ok"`). The `ack` helper is removed — use `Response.json(body, init)`.
|
|
@@ -14,73 +34,68 @@ Breaking changes:
|
|
|
14
34
|
|
|
15
35
|
Behavior kept: hook execution order, prefix semantics (default `/webhooks/`), exact-match routing, HMAC verification, and the `404`/`400`/`500` error body shapes. Unknown routes now return `404` without reading the request body.
|
|
16
36
|
|
|
17
|
-
##
|
|
37
|
+
## [0.3.0]
|
|
18
38
|
|
|
19
|
-
###
|
|
39
|
+
### Changed
|
|
20
40
|
|
|
21
41
|
`Adapter` and `BaseAdapter` are now generic over the framework request/response types (`Adapter<TReq, TRes>`, `BaseAdapter<TReq, TRes>`), replacing the previous per-method generics. The mapping members (`toNormalizedRequest`, `toFrameworkResponse`, `handleWebhook`) are now arrow properties, so custom adapters must override them with property syntax rather than method syntax.
|
|
22
42
|
|
|
23
|
-
Also: `register()` now returns `this`, error hooks always receive a real `Error` instance, and `rawBody` is typed as `Uint8Array`.
|
|
24
|
-
|
|
25
|
-
# @zap-studio/webhooks
|
|
43
|
+
Also: `register()` now returns `this`, error hooks always receive a real `Error` instance, and `rawBody` is typed as `Uint8Array`. Internal formatting and lint cleanup migrated to ultracite.
|
|
26
44
|
|
|
27
|
-
## 0.2.2
|
|
45
|
+
## [0.2.2]
|
|
28
46
|
|
|
29
|
-
###
|
|
47
|
+
### Changed
|
|
30
48
|
|
|
31
49
|
- Updated dependency `@zap-studio/validation` to `0.3.4`.
|
|
32
50
|
|
|
33
|
-
## 0.2.1
|
|
34
|
-
|
|
35
|
-
### Fixed
|
|
36
|
-
|
|
37
|
-
- 3a950dc: Preserve registered hook assignment types while keeping the schema-first router API unchanged.
|
|
51
|
+
## [0.2.1]
|
|
38
52
|
|
|
39
53
|
### Changed
|
|
40
54
|
|
|
41
55
|
- 5fa58b1: Reduced webhook router complexity by consolidating hook normalization and handler entry creation.
|
|
42
56
|
- 7004e9f: Allow explicit `undefined` in option handling, then follow with d707800 to remove redundant `| undefined` unions from public types.
|
|
43
57
|
- 9f31f87: Switched the package build to ESNext-aligned output and updated package tooling and publish metadata.
|
|
58
|
+
- Updated dependency `@zap-studio/validation` to `0.3.3`.
|
|
44
59
|
|
|
45
|
-
###
|
|
60
|
+
### Fixed
|
|
46
61
|
|
|
47
|
-
-
|
|
62
|
+
- 3a950dc: Preserve registered hook assignment types while keeping the schema-first router API unchanged.
|
|
48
63
|
|
|
49
|
-
## 0.2.0
|
|
64
|
+
## [0.2.0]
|
|
50
65
|
|
|
51
|
-
###
|
|
66
|
+
### Changed
|
|
52
67
|
|
|
53
68
|
- c686862: Switch `createHmacVerifier` to Web Crypto and standardize the verifier around string secrets.
|
|
54
69
|
|
|
55
70
|
This change removes the Node `crypto` dependency from the verifier path, keeps `req.rawBody` as `Uint8Array`, simplifies `createHmacVerifier` to take a string secret, and adds public `VerificationError` in `@zap-studio/webhooks/errors` for verifier setup and signature failures.
|
|
56
71
|
|
|
57
|
-
## 0.1.4
|
|
72
|
+
## [0.1.4]
|
|
58
73
|
|
|
59
|
-
###
|
|
74
|
+
### Changed
|
|
60
75
|
|
|
61
76
|
- e26293e: Updated dependencies.
|
|
62
77
|
- @zap-studio/validation@0.3.2
|
|
63
78
|
|
|
64
|
-
## 0.1.3
|
|
79
|
+
## [0.1.3]
|
|
65
80
|
|
|
66
|
-
###
|
|
81
|
+
### Changed
|
|
67
82
|
|
|
68
83
|
- 5ea3d3b: Updated dependencies.
|
|
69
84
|
- @zap-studio/validation@0.3.1
|
|
70
85
|
|
|
71
|
-
## 0.1.2
|
|
86
|
+
## [0.1.2]
|
|
72
87
|
|
|
73
|
-
###
|
|
88
|
+
### Fixed
|
|
74
89
|
|
|
75
90
|
- c209a27: Fix payload schema validation internals to use the current async `standardValidate` options API (`{ throwOnError: false }`), restoring typecheck compatibility after the validation helper signature update.
|
|
76
91
|
|
|
77
|
-
## 0.1.1
|
|
92
|
+
## [0.1.1]
|
|
78
93
|
|
|
79
|
-
###
|
|
94
|
+
### Changed
|
|
80
95
|
|
|
81
96
|
- f75b984: Updated dependency `@zap-studio/validation` to `0.3.0`.
|
|
82
97
|
|
|
83
|
-
## 0.1.0
|
|
98
|
+
## [0.1.0]
|
|
84
99
|
|
|
85
100
|
### Added
|
|
86
101
|
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -2,66 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
Schema-first, type-safe webhook routing built on the standard Web API [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) primitives, with runtime-agnostic signature verification support.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Full documentation: [zapstudio.dev/webhooks](https://www.zapstudio.dev/webhooks)
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
Webhook handlers usually repeat the same plumbing:
|
|
10
|
-
|
|
11
|
-
- verify request authenticity
|
|
12
|
-
- parse and validate payloads
|
|
13
|
-
- route by event path
|
|
14
|
-
- normalize success/error responses
|
|
15
|
-
|
|
16
|
-
`@zap-studio/webhooks` isolates that plumbing so your handler code stays focused on business logic.
|
|
17
|
-
|
|
18
|
-
Schemas are the source of truth, and payload types are inferred from them.
|
|
19
|
-
|
|
20
|
-
## Install
|
|
7
|
+
## Installation
|
|
21
8
|
|
|
22
9
|
```bash
|
|
23
10
|
npm install @zap-studio/webhooks
|
|
24
11
|
```
|
|
25
12
|
|
|
26
|
-
|
|
13
|
+
You also need a schema library that implements [Standard Schema](https://github.com/standard-schema/standard-schema), such as Zod, Valibot, or ArkType.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Web API native** — `handle(request: Request)` returns a `Response`, so the router plugs directly into Bun, Deno, Cloudflare Workers, Next.js route handlers, Hono, and any other fetch-compatible runtime.
|
|
18
|
+
- **Type-safe routing** — handler payload types are inferred from the route schema.
|
|
19
|
+
- **Standard Schema validation** — bring Zod, Valibot, ArkType, or any compatible library.
|
|
20
|
+
- **Signature verification** — built-in HMAC verifier with constant-time comparison, or plug in your own `verify` function.
|
|
21
|
+
- **Lifecycle hooks** — global `before`, `after`, and `onError` hooks for cross-cutting behavior.
|
|
22
|
+
- **Runtime-agnostic** — uses the Web Crypto API, not Node-specific APIs.
|
|
23
|
+
- **Tree-shakeable** — validation and hook-running internals are standalone functions; unused exports are dropped by any modern bundler.
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
27
26
|
|
|
28
27
|
```ts
|
|
29
28
|
import { createWebhookRouter } from "@zap-studio/webhooks";
|
|
30
29
|
import { z } from "zod";
|
|
31
30
|
|
|
32
|
-
const router = createWebhookRouter({
|
|
33
|
-
prefix: "/webhooks", // default
|
|
34
|
-
});
|
|
31
|
+
const router = createWebhookRouter({ prefix: "/webhooks" });
|
|
35
32
|
|
|
36
33
|
router.register("/payments/succeeded", {
|
|
37
|
-
schema: z.object({
|
|
38
|
-
id: z.string(),
|
|
39
|
-
amount: z.number().positive(),
|
|
40
|
-
currency: z.string().length(3),
|
|
41
|
-
}),
|
|
34
|
+
schema: z.object({ id: z.string(), amount: z.number().positive() }),
|
|
42
35
|
handler: ({ payload }) => {
|
|
43
36
|
// payload is inferred from schema
|
|
44
|
-
return Response.json(
|
|
37
|
+
return Response.json({ processed: payload.id });
|
|
45
38
|
},
|
|
46
39
|
});
|
|
47
40
|
|
|
48
|
-
// Any fetch-compatible runtime: Bun, Deno, Cloudflare Workers, ...
|
|
49
41
|
export default {
|
|
50
42
|
fetch: (request: Request) => router.handle(request),
|
|
51
43
|
};
|
|
52
44
|
```
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Handlers can return a `Response`, or `undefined` to let the router reply with its default `200` acknowledgement.
|
|
57
|
-
|
|
58
|
-
### Paths and the prefix
|
|
59
|
-
|
|
60
|
-
Routes are registered with a leading slash (`"/payments/succeeded"`) and matched relative to the router's `prefix` (default `"/webhooks"`, no trailing slash) — so the example above answers on `/webhooks/payments/succeeded`. Paths are normalized internally: missing leading slashes are added, trailing slashes stripped, and duplicate slashes collapsed, on both registered routes and incoming request URLs. Set `prefix: ""` (or `"/"`) to mount routes at the root.
|
|
61
|
-
|
|
62
|
-
## Runtime integration
|
|
46
|
+
## Web API Native
|
|
63
47
|
|
|
64
|
-
|
|
48
|
+
`handle(request: Request)` returns a `Response`, so the router plugs directly into any fetch-compatible runtime.
|
|
65
49
|
|
|
66
50
|
```ts
|
|
67
51
|
// Bun / Deno / Cloudflare Workers
|
|
@@ -74,151 +58,82 @@ export const POST = (request: Request) => router.handle(request);
|
|
|
74
58
|
app.all("/webhooks/*", (c) => router.handle(c.req.raw));
|
|
75
59
|
```
|
|
76
60
|
|
|
77
|
-
|
|
61
|
+
## Type-Safe Routing
|
|
78
62
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Hooks, verifiers, and handlers all receive a context object instead of the raw request stream. The router reads the request body exactly once, so the exact bytes stay available for signature verification:
|
|
63
|
+
Handler payload types are inferred from the route schema.
|
|
82
64
|
|
|
83
65
|
```ts
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
66
|
+
router.register("/payments/succeeded", {
|
|
67
|
+
schema: z.object({ id: z.string(), amount: z.number() }),
|
|
68
|
+
handler: ({ payload }) => {
|
|
69
|
+
// payload.id: string, payload.amount: number — inferred from schema
|
|
70
|
+
return Response.json({ ok: true });
|
|
71
|
+
},
|
|
72
|
+
});
|
|
89
73
|
```
|
|
90
74
|
|
|
91
|
-
|
|
75
|
+
## Standard Schema Validation
|
|
92
76
|
|
|
93
|
-
|
|
77
|
+
Bring Zod, Valibot, ArkType, or any compatible library.
|
|
94
78
|
|
|
95
79
|
```ts
|
|
96
|
-
import { createHmacVerifier, createWebhookRouter } from "@zap-studio/webhooks";
|
|
97
80
|
import { z } from "zod";
|
|
81
|
+
// or: import * as v from "valibot"; import { type } from "arktype";
|
|
98
82
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
secret: process.env.GITHUB_WEBHOOK_SECRET!,
|
|
103
|
-
}),
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
router.register("/github/push", {
|
|
107
|
-
schema: z.object({
|
|
108
|
-
ref: z.string(),
|
|
109
|
-
repository: z.object({
|
|
110
|
-
full_name: z.string(),
|
|
111
|
-
}),
|
|
112
|
-
}),
|
|
113
|
-
handler: ({ payload }) => {
|
|
114
|
-
console.log(`[github] ${payload.repository.full_name} ${payload.ref}`);
|
|
115
|
-
return undefined; // default 200 "ok"
|
|
116
|
-
},
|
|
83
|
+
router.register("/event", {
|
|
84
|
+
schema: z.object({ id: z.string() }),
|
|
85
|
+
handler: ({ payload }) => Response.json(payload),
|
|
117
86
|
});
|
|
118
87
|
```
|
|
119
88
|
|
|
120
|
-
##
|
|
89
|
+
## Signature Verification
|
|
121
90
|
|
|
122
|
-
|
|
123
|
-
import Stripe from "stripe";
|
|
124
|
-
import { createWebhookRouter } from "@zap-studio/webhooks";
|
|
125
|
-
import { z } from "zod";
|
|
91
|
+
Built-in HMAC verifier with constant-time comparison, or plug in your own `verify` function.
|
|
126
92
|
|
|
127
|
-
|
|
93
|
+
```ts
|
|
94
|
+
import {
|
|
95
|
+
createHmacVerifier,
|
|
96
|
+
createWebhookRouter,
|
|
97
|
+
VerificationError,
|
|
98
|
+
} from "@zap-studio/webhooks";
|
|
128
99
|
|
|
129
100
|
const router = createWebhookRouter({
|
|
130
|
-
verify: ({
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
throw new Error("Missing Stripe signature");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
stripe.webhooks.constructEvent(
|
|
137
|
-
Buffer.from(rawBody),
|
|
138
|
-
signature,
|
|
139
|
-
process.env.STRIPE_WEBHOOK_SECRET!
|
|
140
|
-
);
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
router.register("/stripe/payment_intent.succeeded", {
|
|
145
|
-
schema: z.object({
|
|
146
|
-
id: z.string(),
|
|
147
|
-
object: z.literal("event"),
|
|
148
|
-
type: z.literal("payment_intent.succeeded"),
|
|
101
|
+
verify: createHmacVerifier({
|
|
102
|
+
headerName: "x-hub-signature-256",
|
|
103
|
+
secret: process.env.WEBHOOK_SECRET!,
|
|
149
104
|
}),
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
105
|
+
onError: (error) => {
|
|
106
|
+
if (error instanceof VerificationError) {
|
|
107
|
+
return Response.json({ error: "invalid signature" }, { status: 401 });
|
|
108
|
+
}
|
|
153
109
|
},
|
|
154
110
|
});
|
|
155
111
|
```
|
|
156
112
|
|
|
157
|
-
## Lifecycle
|
|
158
|
-
|
|
159
|
-
Lifecycle hooks let you apply cross-cutting behavior without duplicating code in each handler:
|
|
113
|
+
## Lifecycle Hooks
|
|
160
114
|
|
|
161
|
-
|
|
162
|
-
- `after`: run logic after successful handler execution (metrics, audit logs)
|
|
163
|
-
- `onError`: map thrown errors to consistent responses and centralize error reporting
|
|
115
|
+
Global `before`, `after`, and `onError` hooks for cross-cutting behavior.
|
|
164
116
|
|
|
165
117
|
```ts
|
|
166
118
|
const router = createWebhookRouter({
|
|
167
|
-
before: (ctx) =>
|
|
168
|
-
|
|
169
|
-
},
|
|
170
|
-
after: (_ctx, response) => {
|
|
171
|
-
console.log("status", response.status);
|
|
172
|
-
},
|
|
119
|
+
before: (ctx) => console.log("incoming", ctx.path),
|
|
120
|
+
after: (_ctx, response) => console.log("status", response.status),
|
|
173
121
|
onError: (error) => Response.json({ error: error.message }, { status: 500 }),
|
|
174
122
|
});
|
|
175
123
|
```
|
|
176
124
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
```ts
|
|
180
|
-
const router = createWebhookRouter({
|
|
181
|
-
after: async (_ctx, response) => {
|
|
182
|
-
const body = await response.clone().json();
|
|
183
|
-
console.log("responded with", body);
|
|
184
|
-
},
|
|
185
|
-
});
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## Verification helper
|
|
189
|
-
|
|
190
|
-
`@zap-studio/webhooks` exports `createHmacVerifier`, a small helper that builds a `verify` function for HMAC-signed webhook providers.
|
|
191
|
-
|
|
192
|
-
It does not depend on Node APIs. The verifier uses the Web Crypto API, so it works in any runtime that provides `globalThis.crypto.subtle`.
|
|
125
|
+
## Runtime-Agnostic
|
|
193
126
|
|
|
194
|
-
|
|
195
|
-
- computes an HMAC from `ctx.rawBody`
|
|
196
|
-
- compares signatures in constant time
|
|
197
|
-
- uses the Web Crypto API instead of Node `crypto`
|
|
198
|
-
- works across runtimes that provide `globalThis.crypto.subtle`
|
|
199
|
-
- expects a string secret
|
|
200
|
-
- throws `VerificationError` on verifier setup or signature failures
|
|
127
|
+
Uses the Web Crypto API, not Node-specific APIs.
|
|
201
128
|
|
|
202
129
|
```ts
|
|
203
|
-
|
|
204
|
-
|
|
130
|
+
// Uses globalThis.crypto.subtle — no Node `crypto` import required
|
|
205
131
|
const verify = createHmacVerifier({
|
|
206
132
|
headerName: "x-hub-signature-256",
|
|
207
133
|
secret: process.env.WEBHOOK_SECRET!,
|
|
208
|
-
algo: "sha256", // optional, defaults to sha256
|
|
209
134
|
});
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
await verify(ctx);
|
|
213
|
-
} catch (error) {
|
|
214
|
-
if (error instanceof VerificationError) {
|
|
215
|
-
console.error("webhook verification failed", error.message);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
135
|
```
|
|
219
136
|
|
|
220
|
-
Use this when your provider uses standard HMAC signatures. For providers with custom signing formats, pass your own `verify` function.
|
|
221
|
-
|
|
222
137
|
## Runtime Support
|
|
223
138
|
|
|
224
139
|
| Runtime | Minimum version |
|
package/dist/errors.d.ts
CHANGED
|
@@ -9,6 +9,22 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This error is used by verifier helpers such as `createHmacVerifier` so
|
|
11
11
|
* callers can distinguish verification failures from other webhook errors.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { VerificationError } from "@zap-studio/webhooks";
|
|
16
|
+
*
|
|
17
|
+
* const response = await router.handle(request);
|
|
18
|
+
* // Verification failures surface as a 500 response by default, or via onError:
|
|
19
|
+
* const routerWithHandler = createWebhookRouter({
|
|
20
|
+
* verify: createHmacVerifier({ headerName: "x-signature", secret }),
|
|
21
|
+
* onError: (error) => {
|
|
22
|
+
* if (error instanceof VerificationError) {
|
|
23
|
+
* return Response.json({ error: error.message }, { status: 401 });
|
|
24
|
+
* }
|
|
25
|
+
* },
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
12
28
|
*/
|
|
13
29
|
declare class VerificationError extends Error {
|
|
14
30
|
/**
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4Ba,0BAA0B;;;;;;EAMrC,YAAY"}
|
package/dist/errors.js
CHANGED
|
@@ -9,6 +9,22 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This error is used by verifier helpers such as `createHmacVerifier` so
|
|
11
11
|
* callers can distinguish verification failures from other webhook errors.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { VerificationError } from "@zap-studio/webhooks";
|
|
16
|
+
*
|
|
17
|
+
* const response = await router.handle(request);
|
|
18
|
+
* // Verification failures surface as a 500 response by default, or via onError:
|
|
19
|
+
* const routerWithHandler = createWebhookRouter({
|
|
20
|
+
* verify: createHmacVerifier({ headerName: "x-signature", secret }),
|
|
21
|
+
* onError: (error) => {
|
|
22
|
+
* if (error instanceof VerificationError) {
|
|
23
|
+
* return Response.json({ error: error.message }, { status: 401 });
|
|
24
|
+
* }
|
|
25
|
+
* },
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
12
28
|
*/
|
|
13
29
|
var VerificationError = class extends Error {
|
|
14
30
|
/**
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Error primitives for webhook verification failures.\n *\n * @module @zap-studio/webhooks/errors\n */\n\n/**\n * Error thrown when webhook request verification fails.\n *\n * This error is used by verifier helpers such as `createHmacVerifier` so\n * callers can distinguish verification failures from other webhook errors.\n */\nexport class VerificationError extends Error {\n /**\n * Creates a verification error with a human-readable message.\n *\n * @param message - Error message describing the verification failure.\n */\n constructor(message: string) {\n super(message);\n this.name = \"VerificationError\";\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Error primitives for webhook verification failures.\n *\n * @module @zap-studio/webhooks/errors\n */\n\n/**\n * Error thrown when webhook request verification fails.\n *\n * This error is used by verifier helpers such as `createHmacVerifier` so\n * callers can distinguish verification failures from other webhook errors.\n *\n * @example\n * ```ts\n * import { VerificationError } from \"@zap-studio/webhooks\";\n *\n * const response = await router.handle(request);\n * // Verification failures surface as a 500 response by default, or via onError:\n * const routerWithHandler = createWebhookRouter({\n * verify: createHmacVerifier({ headerName: \"x-signature\", secret }),\n * onError: (error) => {\n * if (error instanceof VerificationError) {\n * return Response.json({ error: error.message }, { status: 401 });\n * }\n * },\n * });\n * ```\n */\nexport class VerificationError extends Error {\n /**\n * Creates a verification error with a human-readable message.\n *\n * @param message - Error message describing the verification failure.\n */\n constructor(message: string) {\n super(message);\n this.name = \"VerificationError\";\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,IAAa,oBAAb,cAAuC,MAAM;;;;;;CAM3C,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { VerificationError } from "./errors.js";
|
|
2
|
-
import { AfterHook, BeforeHook, ErrorHook, HandlerContext, HandlerMap, InferSchemaOutput, InferWebhookMapFromRoutes, RegisterOptions, SchemaRouteOptions, SchemaRoutes, VerifyFn, WebhookContext, WebhookHandler } from "./types.js";
|
|
3
|
-
import { WebhookRouter,
|
|
4
|
-
import { constantTimeEquals } from "./
|
|
5
|
-
import { createHmacVerifier } from "./verify.js";
|
|
2
|
+
import { AfterHook, BeforeHook, ErrorHook, HandlerContext, HandlerMap, InferSchemaOutput, InferWebhookMapFromRoutes, RegisterOptions, SchemaRouteOptions, SchemaRoutes, VerifyFn, WebhookContext, WebhookHandler, WebhookRouterOptions } from "./types.js";
|
|
3
|
+
import { WebhookRouter, createWebhookRouter } from "./router.js";
|
|
4
|
+
import { constantTimeEquals, createHmacVerifier } from "./verify.js";
|
|
6
5
|
export { type AfterHook, type BeforeHook, type ErrorHook, type HandlerContext, type HandlerMap, type InferSchemaOutput, type InferWebhookMapFromRoutes, type RegisterOptions, type SchemaRouteOptions, type SchemaRoutes, VerificationError, type VerifyFn, type WebhookContext, type WebhookHandler, WebhookRouter, type WebhookRouterOptions, constantTimeEquals, createHmacVerifier, createWebhookRouter };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { VerificationError } from "./errors.js";
|
|
2
2
|
import { WebhookRouter, createWebhookRouter } from "./router.js";
|
|
3
|
-
import { constantTimeEquals } from "./
|
|
4
|
-
import { createHmacVerifier } from "./verify.js";
|
|
3
|
+
import { constantTimeEquals, createHmacVerifier } from "./verify.js";
|
|
5
4
|
export { VerificationError, WebhookRouter, constantTimeEquals, createHmacVerifier, createWebhookRouter };
|
package/dist/router.d.ts
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InferSchemaOutput, RegisterOptions, SchemaRouteOptions, WebhookHandler, WebhookRouterOptions } from "./types.js";
|
|
2
2
|
import { StandardSchemaV1 } from "@zap-studio/validation";
|
|
3
3
|
//#region src/router.d.ts
|
|
4
|
-
interface WebhookRouterOptions {
|
|
5
|
-
/** Global hooks executed after successful route handler completion. */
|
|
6
|
-
after?: AfterHook | AfterHook[];
|
|
7
|
-
/** Global hooks executed before route-level hooks and verification. */
|
|
8
|
-
before?: BeforeHook | BeforeHook[];
|
|
9
|
-
/** Global error hook used to override the default `500` response. */
|
|
10
|
-
onError?: ErrorHook;
|
|
11
|
-
/**
|
|
12
|
-
* Required path prefix for all webhook routes. Defaults to `"/webhooks"`.
|
|
13
|
-
*
|
|
14
|
-
* Normalized internally: leading slash added, trailing slash stripped,
|
|
15
|
-
* duplicate slashes collapsed. Use `""` or `"/"` to mount at the root.
|
|
16
|
-
*/
|
|
17
|
-
prefix?: string;
|
|
18
|
-
/** Optional request verification function (for signature checks, auth, etc.). */
|
|
19
|
-
verify?: VerifyFn;
|
|
20
|
-
}
|
|
21
4
|
/**
|
|
22
5
|
* Main webhook router class.
|
|
23
6
|
*
|
|
24
7
|
* Register routes with typed schemas and call `handle` with a Web API `Request`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { WebhookRouter } from "@zap-studio/webhooks";
|
|
12
|
+
*
|
|
13
|
+
* const router = new WebhookRouter({ prefix: "/webhooks" });
|
|
14
|
+
*
|
|
15
|
+
* router.register("/stripe", {
|
|
16
|
+
* schema: stripeEventSchema,
|
|
17
|
+
* handler: async ({ payload }) => {
|
|
18
|
+
* console.log("Stripe event:", payload.type);
|
|
19
|
+
* },
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* export default { fetch: (request: Request) => router.handle(request) };
|
|
23
|
+
* ```
|
|
25
24
|
*/
|
|
26
25
|
declare class WebhookRouter<TMap = unknown> {
|
|
27
26
|
private readonly handlers;
|
|
@@ -35,6 +34,15 @@ declare class WebhookRouter<TMap = unknown> {
|
|
|
35
34
|
* Creates a webhook router with optional global hooks and verification behavior.
|
|
36
35
|
*
|
|
37
36
|
* @param opts - Router-level options.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const router = new WebhookRouter({
|
|
41
|
+
* prefix: "/webhooks",
|
|
42
|
+
* verify: createHmacVerifier({ headerName: "x-signature", secret }),
|
|
43
|
+
* onError: (error) => Response.json({ error: error.message }, { status: 500 }),
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
38
46
|
*/
|
|
39
47
|
constructor(opts?: WebhookRouterOptions);
|
|
40
48
|
/**
|
|
@@ -45,9 +53,46 @@ declare class WebhookRouter<TMap = unknown> {
|
|
|
45
53
|
* @param path - Route path relative to configured prefix, starting with `/` (e.g. `"/stripe"`).
|
|
46
54
|
* @param handlerOrOptions - Handler function or schema-based registration options.
|
|
47
55
|
* @returns The same router instance with an updated internal route type map.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* router.register("/stripe", {
|
|
60
|
+
* schema: stripeEventSchema,
|
|
61
|
+
* handler: async ({ payload }) => {
|
|
62
|
+
* console.log(payload.type); // typed from stripeEventSchema
|
|
63
|
+
* },
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
48
66
|
*/
|
|
49
67
|
register<Path extends `/${string}`, TSchema extends StandardSchemaV1<unknown, unknown>>(path: Path, handlerOrOptions: SchemaRouteOptions<TSchema>): WebhookRouter<TMap & Record<Path, InferSchemaOutput<TSchema>>>;
|
|
68
|
+
/**
|
|
69
|
+
* Register a webhook handler for a specific path, with schema-less registration options.
|
|
70
|
+
*
|
|
71
|
+
* @param path - Route path relative to configured prefix, starting with `/` (e.g. `"/stripe"`).
|
|
72
|
+
* @param handlerOrOptions - Registration options without a schema.
|
|
73
|
+
* @returns The same router instance with an updated internal route type map.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* router.register("/ping", {
|
|
78
|
+
* before: (ctx) => console.log("received", ctx.path),
|
|
79
|
+
* handler: () => Response.json({ ok: true }),
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
50
83
|
register<Path extends `/${string}`, TPayload>(path: Path, handlerOrOptions: RegisterOptions<TPayload>): WebhookRouter<TMap & Record<Path, TPayload>>;
|
|
84
|
+
/**
|
|
85
|
+
* Register a webhook handler for a specific path, using a plain handler function.
|
|
86
|
+
*
|
|
87
|
+
* @param path - Route path relative to configured prefix, starting with `/` (e.g. `"/stripe"`).
|
|
88
|
+
* @param handlerOrOptions - Handler function to process the webhook.
|
|
89
|
+
* @returns The same router instance with an updated internal route type map.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* router.register("/health", () => Response.json({ status: "ok" }));
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
51
96
|
register<Path extends `/${string}`>(path: Path, handlerOrOptions: WebhookHandler): WebhookRouter<TMap & Record<Path, unknown>>;
|
|
52
97
|
/**
|
|
53
98
|
* Handles an incoming webhook request.
|
|
@@ -57,15 +102,19 @@ declare class WebhookRouter<TMap = unknown> {
|
|
|
57
102
|
*
|
|
58
103
|
* @param request - Incoming Web API request.
|
|
59
104
|
* @returns Web API response for the runtime to send back.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* // Framework-agnostic: works with any Web API Request/Response runtime.
|
|
109
|
+
* export async function POST(request: Request): Promise<Response> {
|
|
110
|
+
* return router.handle(request);
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
60
113
|
*/
|
|
61
114
|
handle(request: Request): Promise<Response>;
|
|
115
|
+
/** Resolves the incoming request's URL to a registered route key, or `null` if it doesn't match the configured prefix. */
|
|
62
116
|
private matchPath;
|
|
63
|
-
|
|
64
|
-
private static runAfterHooks;
|
|
65
|
-
private static createHandlerEntry;
|
|
66
|
-
private static parseRequestBody;
|
|
67
|
-
private static validatePayload;
|
|
68
|
-
private static executeHandler;
|
|
117
|
+
/** Builds the error response for a failed request, deferring to the global error hook when set. */
|
|
69
118
|
private handleError;
|
|
70
119
|
}
|
|
71
120
|
/**
|
|
@@ -73,8 +122,16 @@ declare class WebhookRouter<TMap = unknown> {
|
|
|
73
122
|
*
|
|
74
123
|
* @param opts - Optional global router options.
|
|
75
124
|
* @returns A new webhook router.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* import { createWebhookRouter } from "@zap-studio/webhooks";
|
|
129
|
+
*
|
|
130
|
+
* const router = createWebhookRouter({ prefix: "/webhooks" });
|
|
131
|
+
* router.register("/stripe", { schema: stripeEventSchema, handler });
|
|
132
|
+
* ```
|
|
76
133
|
*/
|
|
77
134
|
declare const createWebhookRouter: (opts?: WebhookRouterOptions) => WebhookRouter;
|
|
78
135
|
//#endregion
|
|
79
|
-
export { WebhookRouter,
|
|
136
|
+
export { WebhookRouter, createWebhookRouter };
|
|
80
137
|
//# sourceMappingURL=router.d.ts.map
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","names":[],"sources":["../src/router.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"router.d.ts","names":[],"sources":["../src/router.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;cA2La,cAAc;mBACR;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;;;;;;;;;;;;;;;EAgBjB,YAAY,OAAM;;;;;;;;;;;;;;;;;;;;EA4BlB,SACE,2BACA,gBAAgB,oCAEhB,MAAM,MACN,kBAAkB,mBAAmB,WACpC,cAAc,OAAO,OAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;EAgBvD,SAAS,2BAA2B,UAClC,MAAM,MACN,kBAAkB,gBAAgB,YACjC,cAAc,OAAO,OAAO,MAAM;;;;;;;;;;;;;EAarC,SAAS,2BACP,MAAM,MACN,kBAAkB,iBACjB,cAAc,OAAO,OAAO;;;;;;;;;;;;;;;;;;EAgC/B,OAAa,SAAS,UAAU,QAAQ;;UAqDhC;;UAsBM;;;;;;;;;;;;;;;;cAoCH,sBACX,OAAO,yBACN"}
|