@webhooks-cc/sdk 0.2.0 → 0.3.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.md CHANGED
@@ -1,52 +1,69 @@
1
1
  # @webhooks-cc/sdk
2
2
 
3
- TypeScript SDK for [webhooks.cc](https://webhooks.cc). Create temporary webhook endpoints, capture requests, and assert on their contents in your test suite.
3
+ TypeScript SDK for [webhooks.cc](https://webhooks.cc). Create webhook endpoints, capture requests, match and replay them, and stream events in real time.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
8
  npm install @webhooks-cc/sdk
9
+ # or: pnpm add / yarn add / bun add
9
10
  ```
10
11
 
11
- ## Quick Start
12
+ ## Quick start
12
13
 
13
14
  ```typescript
14
- import { WebhooksCC } from "@webhooks-cc/sdk";
15
+ import { WebhooksCC, matchAll, matchMethod, matchHeader } from "@webhooks-cc/sdk";
15
16
 
16
17
  const client = new WebhooksCC({ apiKey: "whcc_..." });
17
18
 
18
- // Create a temporary endpoint
19
- const endpoint = await client.endpoints.create({ name: "My Test" });
19
+ // Create an endpoint
20
+ const endpoint = await client.endpoints.create({ name: "stripe-test" });
20
21
  console.log(endpoint.url); // https://go.webhooks.cc/w/abc123
21
22
 
22
23
  // Point your service at endpoint.url, then wait for the webhook
23
24
  const request = await client.requests.waitFor(endpoint.slug, {
24
- timeout: 10000,
25
- match: (r) => r.method === "POST",
25
+ timeout: "30s",
26
+ match: matchAll(matchMethod("POST"), matchHeader("stripe-signature")),
26
27
  });
27
28
 
28
- console.log(request.body); // '{"event":"order.created"}'
29
- console.log(request.headers); // { 'content-type': 'application/json', ... }
29
+ console.log(request.body); // '{"type":"checkout.session.completed",...}'
30
30
 
31
31
  // Clean up
32
32
  await client.endpoints.delete(endpoint.slug);
33
33
  ```
34
34
 
35
- ## API
35
+ ## Client options
36
36
 
37
- ### `new WebhooksCC(options)`
37
+ ```typescript
38
+ new WebhooksCC(options);
39
+ ```
40
+
41
+ | Option | Type | Default | Description |
42
+ | ------------ | ------------- | ------------------------ | ------------------------- |
43
+ | `apiKey` | `string` | _required_ | API key (`whcc_...`) |
44
+ | `baseUrl` | `string` | `https://webhooks.cc` | API base URL |
45
+ | `webhookUrl` | `string` | `https://go.webhooks.cc` | Webhook receiver URL |
46
+ | `timeout` | `number` | `30000` | HTTP request timeout (ms) |
47
+ | `hooks` | `ClientHooks` | — | Lifecycle callbacks |
38
48
 
39
- | Option | Type | Default | Description |
40
- | --------- | -------- | --------------------- | -------------------- |
41
- | `apiKey` | `string` | _required_ | API key (`whcc_...`) |
42
- | `baseUrl` | `string` | `https://webhooks.cc` | API base URL |
43
- | `timeout` | `number` | `30000` | Request timeout (ms) |
49
+ ### Hooks
50
+
51
+ ```typescript
52
+ const client = new WebhooksCC({
53
+ apiKey: "whcc_...",
54
+ hooks: {
55
+ onRequest: (info) => console.log(info.method, info.url),
56
+ onResponse: (info) => console.log(info.status),
57
+ onError: (info) => console.error(info.error),
58
+ },
59
+ });
60
+ ```
44
61
 
45
- ### Endpoints
62
+ ## Endpoints
46
63
 
47
64
  ```typescript
48
65
  // Create
49
- const endpoint = await client.endpoints.create({ name: "optional name" });
66
+ const endpoint = await client.endpoints.create({ name: "my-test" });
50
67
 
51
68
  // List all
52
69
  const endpoints = await client.endpoints.list();
@@ -54,17 +71,33 @@ const endpoints = await client.endpoints.list();
54
71
  // Get by slug
55
72
  const endpoint = await client.endpoints.get("abc123");
56
73
 
74
+ // Update name or mock response
75
+ await client.endpoints.update("abc123", {
76
+ name: "New Name",
77
+ mockResponse: { status: 201, body: '{"ok":true}', headers: {} },
78
+ });
79
+
80
+ // Clear mock response
81
+ await client.endpoints.update("abc123", { mockResponse: null });
82
+
83
+ // Send a test webhook
84
+ const res = await client.endpoints.send("abc123", {
85
+ method: "POST",
86
+ headers: { "content-type": "application/json" },
87
+ body: { event: "test" },
88
+ });
89
+
57
90
  // Delete
58
91
  await client.endpoints.delete("abc123");
59
92
  ```
60
93
 
61
- ### Requests
94
+ ## Requests
62
95
 
63
96
  ```typescript
64
- // List captured requests for an endpoint
97
+ // List captured requests
65
98
  const requests = await client.requests.list("endpoint-slug", {
66
- limit: 50, // default: 50, max: 1000
67
- since: Date.now() - 60000, // only after this timestamp (ms)
99
+ limit: 50,
100
+ since: Date.now() - 60000,
68
101
  });
69
102
 
70
103
  // Get a single request by ID
@@ -72,27 +105,82 @@ const request = await client.requests.get("request-id");
72
105
 
73
106
  // Poll until a matching request arrives
74
107
  const request = await client.requests.waitFor("endpoint-slug", {
75
- timeout: 30000, // max wait (ms), default: 30000
76
- pollInterval: 500, // poll interval (ms), default: 500
77
- match: (r) => r.method === "POST" && r.body?.includes("order"),
108
+ timeout: "30s", // human-readable or milliseconds
109
+ pollInterval: "500ms",
110
+ match: matchHeader("stripe-signature"),
78
111
  });
112
+
113
+ // Replay a captured request to a target URL
114
+ const res = await client.requests.replay(request.id, "http://localhost:3000/webhooks");
115
+
116
+ // Stream requests in real time via SSE
117
+ for await (const req of client.requests.subscribe("endpoint-slug")) {
118
+ console.log(req.method, req.body);
119
+ }
79
120
  ```
80
121
 
81
- ### Errors
122
+ ## Matchers
123
+
124
+ Composable functions for `waitFor`'s `match` option:
82
125
 
83
126
  ```typescript
84
- import { WebhooksCC, ApiError } from "@webhooks-cc/sdk";
127
+ import { matchMethod, matchHeader, matchBodyPath, matchAll, matchAny } from "@webhooks-cc/sdk";
128
+
129
+ // Match POST requests with a specific header
130
+ matchAll(matchMethod("POST"), matchHeader("x-event-type", "payment.success"));
131
+
132
+ // Match header presence (any value)
133
+ matchHeader("stripe-signature");
134
+
135
+ // Match a nested JSON body field
136
+ matchBodyPath("data.object.id", "sub_123");
137
+
138
+ // Match any of several conditions
139
+ matchAny(matchHeader("stripe-signature"), matchHeader("x-github-event"));
140
+ ```
141
+
142
+ ## Provider helpers
143
+
144
+ Detect webhook sources by their signature headers:
145
+
146
+ ```typescript
147
+ import { isStripeWebhook, isGitHubWebhook } from "@webhooks-cc/sdk";
148
+
149
+ if (isStripeWebhook(request)) {
150
+ // has stripe-signature header
151
+ }
152
+ ```
153
+
154
+ Available: `isStripeWebhook`, `isGitHubWebhook`, `isShopifyWebhook`, `isSlackWebhook`, `isTwilioWebhook`, `isPaddleWebhook`, `isLinearWebhook`.
155
+
156
+ ## Self-description
157
+
158
+ AI agents can call `client.describe()` to get a structured summary of all SDK operations, parameters, and return types — no API call required.
159
+
160
+ ```typescript
161
+ const desc = client.describe();
162
+ // { version: "0.3.0", endpoints: { create: { ... }, ... }, requests: { ... } }
163
+ ```
164
+
165
+ ## Errors
166
+
167
+ All API errors extend `WebhooksCCError` and include actionable recovery hints:
168
+
169
+ ```typescript
170
+ import { WebhooksCC, WebhooksCCError, NotFoundError } from "@webhooks-cc/sdk";
85
171
 
86
172
  try {
87
173
  await client.endpoints.get("nonexistent");
88
174
  } catch (error) {
89
- if (error instanceof ApiError) {
175
+ if (error instanceof WebhooksCCError) {
90
176
  console.log(error.statusCode); // 404
91
- console.log(error.message); // "API error (404): ..."
177
+ console.log(error.message); // includes what went wrong and how to fix it
92
178
  }
93
179
  }
94
180
  ```
95
181
 
182
+ Error classes: `WebhooksCCError`, `UnauthorizedError`, `NotFoundError`, `TimeoutError`, `RateLimitError`. The legacy `ApiError` alias is still exported for backward compatibility.
183
+
96
184
  ## GitHub Actions
97
185
 
98
186
  Add your API key as a repository secret named `WHK_API_KEY`:
@@ -107,14 +195,14 @@ Add your API key as a repository secret named `WHK_API_KEY`:
107
195
  ```typescript
108
196
  // webhook.test.ts
109
197
  import { describe, it, expect, afterAll } from "vitest";
110
- import { WebhooksCC } from "@webhooks-cc/sdk";
198
+ import { WebhooksCC, matchHeader } from "@webhooks-cc/sdk";
111
199
 
112
200
  const client = new WebhooksCC({ apiKey: process.env.WHK_API_KEY! });
113
201
 
114
202
  describe("webhook integration", () => {
115
203
  let slug: string;
116
204
 
117
- it("receives order webhook", async () => {
205
+ it("receives Stripe webhook", async () => {
118
206
  const endpoint = await client.endpoints.create({ name: "CI Test" });
119
207
  slug = endpoint.slug;
120
208
 
@@ -123,12 +211,12 @@ describe("webhook integration", () => {
123
211
  await yourService.createOrder();
124
212
 
125
213
  const req = await client.requests.waitFor(slug, {
126
- timeout: 15000,
127
- match: (r) => r.body?.includes("order.created"),
214
+ timeout: "15s",
215
+ match: matchHeader("stripe-signature"),
128
216
  });
129
217
 
130
218
  const body = JSON.parse(req.body!);
131
- expect(body.event).toBe("order.created");
219
+ expect(body.type).toBe("checkout.session.completed");
132
220
  });
133
221
 
134
222
  afterAll(async () => {
@@ -144,11 +232,16 @@ All types are exported:
144
232
  ```typescript
145
233
  import type {
146
234
  ClientOptions,
235
+ ClientHooks,
147
236
  Endpoint,
148
237
  Request,
149
238
  CreateEndpointOptions,
239
+ UpdateEndpointOptions,
240
+ SendOptions,
150
241
  ListRequestsOptions,
151
242
  WaitForOptions,
243
+ SubscribeOptions,
244
+ SDKDescription,
152
245
  } from "@webhooks-cc/sdk";
153
246
  ```
154
247
 
package/dist/index.d.mts CHANGED
@@ -49,6 +49,30 @@ interface CreateEndpointOptions {
49
49
  /** Display name for the endpoint */
50
50
  name?: string;
51
51
  }
52
+ /**
53
+ * Options for updating an existing endpoint.
54
+ */
55
+ interface UpdateEndpointOptions {
56
+ /** New display name */
57
+ name?: string;
58
+ /** Mock response config, or null to clear */
59
+ mockResponse?: {
60
+ status: number;
61
+ body: string;
62
+ headers: Record<string, string>;
63
+ } | null;
64
+ }
65
+ /**
66
+ * Options for sending a test webhook to an endpoint.
67
+ */
68
+ interface SendOptions {
69
+ /** HTTP method (default: "POST") */
70
+ method?: string;
71
+ /** HTTP headers to include */
72
+ headers?: Record<string, string>;
73
+ /** Request body (will be JSON-serialized if not a string) */
74
+ body?: unknown;
75
+ }
52
76
  /**
53
77
  * Options for listing captured requests.
54
78
  */
@@ -62,13 +86,22 @@ interface ListRequestsOptions {
62
86
  * Options for waitFor() polling behavior.
63
87
  */
64
88
  interface WaitForOptions {
65
- /** Maximum time to wait in milliseconds (default: 30000) */
66
- timeout?: number;
67
- /** Interval between polls in milliseconds (default: 500, min: 10, max: 60000) */
68
- pollInterval?: number;
89
+ /** Maximum time to wait (ms or duration string like "30s", "5m") (default: 30000) */
90
+ timeout?: number | string;
91
+ /** Interval between polls (ms or duration string) (default: 500, min: 10, max: 60000) */
92
+ pollInterval?: number | string;
69
93
  /** Filter function to match specific requests */
70
94
  match?: (request: Request) => boolean;
71
95
  }
96
+ /**
97
+ * Options for subscribe() SSE streaming.
98
+ */
99
+ interface SubscribeOptions {
100
+ /** AbortSignal to cancel the subscription */
101
+ signal?: AbortSignal;
102
+ /** Maximum time to stream (ms or duration string like "30m") */
103
+ timeout?: number | string;
104
+ }
72
105
  /** Info passed to the onRequest hook before a request is sent. */
73
106
  interface RequestHookInfo {
74
107
  method: string;
@@ -105,11 +138,24 @@ interface ClientOptions {
105
138
  apiKey: string;
106
139
  /** Base URL for the API (default: https://webhooks.cc) */
107
140
  baseUrl?: string;
141
+ /** Base URL for sending webhooks (default: https://go.webhooks.cc) */
142
+ webhookUrl?: string;
108
143
  /** Request timeout in milliseconds (default: 30000) */
109
144
  timeout?: number;
110
145
  /** Lifecycle hooks for observability */
111
146
  hooks?: ClientHooks;
112
147
  }
148
+ /** Description of a single SDK operation. */
149
+ interface OperationDescription {
150
+ description: string;
151
+ params: Record<string, string>;
152
+ }
153
+ /** Self-describing schema returned by client.describe(). */
154
+ interface SDKDescription {
155
+ version: string;
156
+ endpoints: Record<string, OperationDescription>;
157
+ requests: Record<string, OperationDescription>;
158
+ }
113
159
 
114
160
  /**
115
161
  * Base error class for all webhooks.cc SDK errors.
@@ -143,11 +189,13 @@ declare class RateLimitError extends WebhooksCCError {
143
189
  *
144
190
  * @example
145
191
  * ```typescript
192
+ * import { WebhooksCC, matchMethod } from '@webhooks-cc/sdk';
193
+ *
146
194
  * const client = new WebhooksCC({ apiKey: 'whcc_...' });
147
195
  * const endpoint = await client.endpoints.create({ name: 'My Webhook' });
148
196
  * const request = await client.requests.waitFor(endpoint.slug, {
149
- * timeout: 10000,
150
- * match: (r) => r.method === 'POST'
197
+ * timeout: '30s',
198
+ * match: matchMethod('POST'),
151
199
  * });
152
200
  * ```
153
201
  */
@@ -166,15 +214,20 @@ declare const ApiError: typeof WebhooksCCError;
166
214
  declare class WebhooksCC {
167
215
  private readonly apiKey;
168
216
  private readonly baseUrl;
217
+ private readonly webhookUrl;
169
218
  private readonly timeout;
170
219
  private readonly hooks;
171
220
  constructor(options: ClientOptions);
172
221
  private request;
222
+ /** Returns a static description of all SDK operations (no API call). */
223
+ describe(): SDKDescription;
173
224
  endpoints: {
174
225
  create: (options?: CreateEndpointOptions) => Promise<Endpoint>;
175
226
  list: () => Promise<Endpoint[]>;
176
227
  get: (slug: string) => Promise<Endpoint>;
228
+ update: (slug: string, options: UpdateEndpointOptions) => Promise<Endpoint>;
177
229
  delete: (slug: string) => Promise<void>;
230
+ send: (slug: string, options?: SendOptions) => Promise<Response>;
178
231
  };
179
232
  requests: {
180
233
  list: (endpointSlug: string, options?: ListRequestsOptions) => Promise<Request[]>;
@@ -192,6 +245,23 @@ declare class WebhooksCC {
192
245
  * @throws Error if timeout expires or max iterations (10000) reached
193
246
  */
194
247
  waitFor: (endpointSlug: string, options?: WaitForOptions) => Promise<Request>;
248
+ /**
249
+ * Replay a captured request to a target URL.
250
+ *
251
+ * Fetches the original request by ID and re-sends it to the specified URL
252
+ * with the original method, headers, and body. Hop-by-hop headers are stripped.
253
+ */
254
+ replay: (requestId: string, targetUrl: string) => Promise<Response>;
255
+ /**
256
+ * Stream incoming requests via SSE as an async iterator.
257
+ *
258
+ * Connects to the SSE endpoint and yields Request objects as they arrive.
259
+ * The connection is closed when the iterator is broken, the signal is aborted,
260
+ * or the timeout expires.
261
+ *
262
+ * No automatic reconnection — if the connection drops, the iterator ends.
263
+ */
264
+ subscribe: (slug: string, options?: SubscribeOptions) => AsyncIterable<Request>;
195
265
  };
196
266
  }
197
267
 
@@ -222,5 +292,65 @@ declare function isGitHubWebhook(request: Request): boolean;
222
292
  * ```
223
293
  */
224
294
  declare function matchJsonField(field: string, value: unknown): (request: Request) => boolean;
295
+ /** Check if a request looks like a Shopify webhook. */
296
+ declare function isShopifyWebhook(request: Request): boolean;
297
+ /** Check if a request looks like a Slack webhook. */
298
+ declare function isSlackWebhook(request: Request): boolean;
299
+ /** Check if a request looks like a Twilio webhook. */
300
+ declare function isTwilioWebhook(request: Request): boolean;
301
+ /** Check if a request looks like a Paddle webhook. */
302
+ declare function isPaddleWebhook(request: Request): boolean;
303
+ /** Check if a request looks like a Linear webhook. */
304
+ declare function isLinearWebhook(request: Request): boolean;
305
+
306
+ /** Match requests by HTTP method (case-insensitive). */
307
+ declare function matchMethod(method: string): (request: Request) => boolean;
308
+ /** Match requests that have a specific header, optionally with a specific value. Header names are matched case-insensitively; values are matched with exact (case-sensitive) equality. */
309
+ declare function matchHeader(name: string, value?: string): (request: Request) => boolean;
310
+ /**
311
+ * Match requests by a dot-notation path into the JSON body.
312
+ * Supports array indexing with numeric path segments (e.g., `"items.0.id"`).
313
+ *
314
+ * @example
315
+ * ```ts
316
+ * matchBodyPath("data.object.id", "obj_123")
317
+ * matchBodyPath("type", "checkout.session.completed")
318
+ * matchBodyPath("items.0.name", "Widget")
319
+ * ```
320
+ */
321
+ declare function matchBodyPath(path: string, value: unknown): (request: Request) => boolean;
322
+ /** Match when ALL matchers return true. Requires at least one matcher. */
323
+ declare function matchAll(first: (request: Request) => boolean, ...rest: Array<(request: Request) => boolean>): (request: Request) => boolean;
324
+ /** Match when ANY matcher returns true. Requires at least one matcher. */
325
+ declare function matchAny(first: (request: Request) => boolean, ...rest: Array<(request: Request) => boolean>): (request: Request) => boolean;
326
+
327
+ /**
328
+ * Parse a duration value into milliseconds.
329
+ *
330
+ * Accepts:
331
+ * - Numbers: passed through as-is (treated as milliseconds)
332
+ * - Numeric strings: `"500"` → 500
333
+ * - Duration strings: `"30s"` → 30000, `"5m"` → 300000, `"1.5s"` → 1500, `"500ms"` → 500
334
+ *
335
+ * @throws {Error} If the input string is not a valid duration format
336
+ */
337
+ declare function parseDuration(input: number | string): number;
338
+
339
+ /** A parsed SSE frame with event type and data. */
340
+ interface SSEFrame {
341
+ event: string;
342
+ data: string;
343
+ }
344
+ /**
345
+ * Async generator that parses SSE frames from a ReadableStream.
346
+ *
347
+ * Handles:
348
+ * - Multi-line `data:` fields (joined with newlines)
349
+ * - `event:` type fields
350
+ * - Comment lines (`: ...`) — yielded with event "comment"
351
+ * - Empty data fields
352
+ * - Frames terminated by blank lines
353
+ */
354
+ declare function parseSSE(stream: ReadableStream<Uint8Array>): AsyncGenerator<SSEFrame, void, undefined>;
225
355
 
226
- export { ApiError, type ClientHooks, type ClientOptions, type CreateEndpointOptions, type Endpoint, type ErrorHookInfo, type ListRequestsOptions, NotFoundError, RateLimitError, type Request, type RequestHookInfo, type ResponseHookInfo, TimeoutError, UnauthorizedError, type WaitForOptions, WebhooksCC, WebhooksCCError, isGitHubWebhook, isStripeWebhook, matchJsonField, parseJsonBody };
356
+ export { ApiError, type ClientHooks, type ClientOptions, type CreateEndpointOptions, type Endpoint, type ErrorHookInfo, type ListRequestsOptions, NotFoundError, type OperationDescription, RateLimitError, type Request, type RequestHookInfo, type ResponseHookInfo, type SDKDescription, type SSEFrame, type SendOptions, type SubscribeOptions, TimeoutError, UnauthorizedError, type UpdateEndpointOptions, type WaitForOptions, WebhooksCC, WebhooksCCError, isGitHubWebhook, isLinearWebhook, isPaddleWebhook, isShopifyWebhook, isSlackWebhook, isStripeWebhook, isTwilioWebhook, matchAll, matchAny, matchBodyPath, matchHeader, matchJsonField, matchMethod, parseDuration, parseJsonBody, parseSSE };
package/dist/index.d.ts CHANGED
@@ -49,6 +49,30 @@ interface CreateEndpointOptions {
49
49
  /** Display name for the endpoint */
50
50
  name?: string;
51
51
  }
52
+ /**
53
+ * Options for updating an existing endpoint.
54
+ */
55
+ interface UpdateEndpointOptions {
56
+ /** New display name */
57
+ name?: string;
58
+ /** Mock response config, or null to clear */
59
+ mockResponse?: {
60
+ status: number;
61
+ body: string;
62
+ headers: Record<string, string>;
63
+ } | null;
64
+ }
65
+ /**
66
+ * Options for sending a test webhook to an endpoint.
67
+ */
68
+ interface SendOptions {
69
+ /** HTTP method (default: "POST") */
70
+ method?: string;
71
+ /** HTTP headers to include */
72
+ headers?: Record<string, string>;
73
+ /** Request body (will be JSON-serialized if not a string) */
74
+ body?: unknown;
75
+ }
52
76
  /**
53
77
  * Options for listing captured requests.
54
78
  */
@@ -62,13 +86,22 @@ interface ListRequestsOptions {
62
86
  * Options for waitFor() polling behavior.
63
87
  */
64
88
  interface WaitForOptions {
65
- /** Maximum time to wait in milliseconds (default: 30000) */
66
- timeout?: number;
67
- /** Interval between polls in milliseconds (default: 500, min: 10, max: 60000) */
68
- pollInterval?: number;
89
+ /** Maximum time to wait (ms or duration string like "30s", "5m") (default: 30000) */
90
+ timeout?: number | string;
91
+ /** Interval between polls (ms or duration string) (default: 500, min: 10, max: 60000) */
92
+ pollInterval?: number | string;
69
93
  /** Filter function to match specific requests */
70
94
  match?: (request: Request) => boolean;
71
95
  }
96
+ /**
97
+ * Options for subscribe() SSE streaming.
98
+ */
99
+ interface SubscribeOptions {
100
+ /** AbortSignal to cancel the subscription */
101
+ signal?: AbortSignal;
102
+ /** Maximum time to stream (ms or duration string like "30m") */
103
+ timeout?: number | string;
104
+ }
72
105
  /** Info passed to the onRequest hook before a request is sent. */
73
106
  interface RequestHookInfo {
74
107
  method: string;
@@ -105,11 +138,24 @@ interface ClientOptions {
105
138
  apiKey: string;
106
139
  /** Base URL for the API (default: https://webhooks.cc) */
107
140
  baseUrl?: string;
141
+ /** Base URL for sending webhooks (default: https://go.webhooks.cc) */
142
+ webhookUrl?: string;
108
143
  /** Request timeout in milliseconds (default: 30000) */
109
144
  timeout?: number;
110
145
  /** Lifecycle hooks for observability */
111
146
  hooks?: ClientHooks;
112
147
  }
148
+ /** Description of a single SDK operation. */
149
+ interface OperationDescription {
150
+ description: string;
151
+ params: Record<string, string>;
152
+ }
153
+ /** Self-describing schema returned by client.describe(). */
154
+ interface SDKDescription {
155
+ version: string;
156
+ endpoints: Record<string, OperationDescription>;
157
+ requests: Record<string, OperationDescription>;
158
+ }
113
159
 
114
160
  /**
115
161
  * Base error class for all webhooks.cc SDK errors.
@@ -143,11 +189,13 @@ declare class RateLimitError extends WebhooksCCError {
143
189
  *
144
190
  * @example
145
191
  * ```typescript
192
+ * import { WebhooksCC, matchMethod } from '@webhooks-cc/sdk';
193
+ *
146
194
  * const client = new WebhooksCC({ apiKey: 'whcc_...' });
147
195
  * const endpoint = await client.endpoints.create({ name: 'My Webhook' });
148
196
  * const request = await client.requests.waitFor(endpoint.slug, {
149
- * timeout: 10000,
150
- * match: (r) => r.method === 'POST'
197
+ * timeout: '30s',
198
+ * match: matchMethod('POST'),
151
199
  * });
152
200
  * ```
153
201
  */
@@ -166,15 +214,20 @@ declare const ApiError: typeof WebhooksCCError;
166
214
  declare class WebhooksCC {
167
215
  private readonly apiKey;
168
216
  private readonly baseUrl;
217
+ private readonly webhookUrl;
169
218
  private readonly timeout;
170
219
  private readonly hooks;
171
220
  constructor(options: ClientOptions);
172
221
  private request;
222
+ /** Returns a static description of all SDK operations (no API call). */
223
+ describe(): SDKDescription;
173
224
  endpoints: {
174
225
  create: (options?: CreateEndpointOptions) => Promise<Endpoint>;
175
226
  list: () => Promise<Endpoint[]>;
176
227
  get: (slug: string) => Promise<Endpoint>;
228
+ update: (slug: string, options: UpdateEndpointOptions) => Promise<Endpoint>;
177
229
  delete: (slug: string) => Promise<void>;
230
+ send: (slug: string, options?: SendOptions) => Promise<Response>;
178
231
  };
179
232
  requests: {
180
233
  list: (endpointSlug: string, options?: ListRequestsOptions) => Promise<Request[]>;
@@ -192,6 +245,23 @@ declare class WebhooksCC {
192
245
  * @throws Error if timeout expires or max iterations (10000) reached
193
246
  */
194
247
  waitFor: (endpointSlug: string, options?: WaitForOptions) => Promise<Request>;
248
+ /**
249
+ * Replay a captured request to a target URL.
250
+ *
251
+ * Fetches the original request by ID and re-sends it to the specified URL
252
+ * with the original method, headers, and body. Hop-by-hop headers are stripped.
253
+ */
254
+ replay: (requestId: string, targetUrl: string) => Promise<Response>;
255
+ /**
256
+ * Stream incoming requests via SSE as an async iterator.
257
+ *
258
+ * Connects to the SSE endpoint and yields Request objects as they arrive.
259
+ * The connection is closed when the iterator is broken, the signal is aborted,
260
+ * or the timeout expires.
261
+ *
262
+ * No automatic reconnection — if the connection drops, the iterator ends.
263
+ */
264
+ subscribe: (slug: string, options?: SubscribeOptions) => AsyncIterable<Request>;
195
265
  };
196
266
  }
197
267
 
@@ -222,5 +292,65 @@ declare function isGitHubWebhook(request: Request): boolean;
222
292
  * ```
223
293
  */
224
294
  declare function matchJsonField(field: string, value: unknown): (request: Request) => boolean;
295
+ /** Check if a request looks like a Shopify webhook. */
296
+ declare function isShopifyWebhook(request: Request): boolean;
297
+ /** Check if a request looks like a Slack webhook. */
298
+ declare function isSlackWebhook(request: Request): boolean;
299
+ /** Check if a request looks like a Twilio webhook. */
300
+ declare function isTwilioWebhook(request: Request): boolean;
301
+ /** Check if a request looks like a Paddle webhook. */
302
+ declare function isPaddleWebhook(request: Request): boolean;
303
+ /** Check if a request looks like a Linear webhook. */
304
+ declare function isLinearWebhook(request: Request): boolean;
305
+
306
+ /** Match requests by HTTP method (case-insensitive). */
307
+ declare function matchMethod(method: string): (request: Request) => boolean;
308
+ /** Match requests that have a specific header, optionally with a specific value. Header names are matched case-insensitively; values are matched with exact (case-sensitive) equality. */
309
+ declare function matchHeader(name: string, value?: string): (request: Request) => boolean;
310
+ /**
311
+ * Match requests by a dot-notation path into the JSON body.
312
+ * Supports array indexing with numeric path segments (e.g., `"items.0.id"`).
313
+ *
314
+ * @example
315
+ * ```ts
316
+ * matchBodyPath("data.object.id", "obj_123")
317
+ * matchBodyPath("type", "checkout.session.completed")
318
+ * matchBodyPath("items.0.name", "Widget")
319
+ * ```
320
+ */
321
+ declare function matchBodyPath(path: string, value: unknown): (request: Request) => boolean;
322
+ /** Match when ALL matchers return true. Requires at least one matcher. */
323
+ declare function matchAll(first: (request: Request) => boolean, ...rest: Array<(request: Request) => boolean>): (request: Request) => boolean;
324
+ /** Match when ANY matcher returns true. Requires at least one matcher. */
325
+ declare function matchAny(first: (request: Request) => boolean, ...rest: Array<(request: Request) => boolean>): (request: Request) => boolean;
326
+
327
+ /**
328
+ * Parse a duration value into milliseconds.
329
+ *
330
+ * Accepts:
331
+ * - Numbers: passed through as-is (treated as milliseconds)
332
+ * - Numeric strings: `"500"` → 500
333
+ * - Duration strings: `"30s"` → 30000, `"5m"` → 300000, `"1.5s"` → 1500, `"500ms"` → 500
334
+ *
335
+ * @throws {Error} If the input string is not a valid duration format
336
+ */
337
+ declare function parseDuration(input: number | string): number;
338
+
339
+ /** A parsed SSE frame with event type and data. */
340
+ interface SSEFrame {
341
+ event: string;
342
+ data: string;
343
+ }
344
+ /**
345
+ * Async generator that parses SSE frames from a ReadableStream.
346
+ *
347
+ * Handles:
348
+ * - Multi-line `data:` fields (joined with newlines)
349
+ * - `event:` type fields
350
+ * - Comment lines (`: ...`) — yielded with event "comment"
351
+ * - Empty data fields
352
+ * - Frames terminated by blank lines
353
+ */
354
+ declare function parseSSE(stream: ReadableStream<Uint8Array>): AsyncGenerator<SSEFrame, void, undefined>;
225
355
 
226
- export { ApiError, type ClientHooks, type ClientOptions, type CreateEndpointOptions, type Endpoint, type ErrorHookInfo, type ListRequestsOptions, NotFoundError, RateLimitError, type Request, type RequestHookInfo, type ResponseHookInfo, TimeoutError, UnauthorizedError, type WaitForOptions, WebhooksCC, WebhooksCCError, isGitHubWebhook, isStripeWebhook, matchJsonField, parseJsonBody };
356
+ export { ApiError, type ClientHooks, type ClientOptions, type CreateEndpointOptions, type Endpoint, type ErrorHookInfo, type ListRequestsOptions, NotFoundError, type OperationDescription, RateLimitError, type Request, type RequestHookInfo, type ResponseHookInfo, type SDKDescription, type SSEFrame, type SendOptions, type SubscribeOptions, TimeoutError, UnauthorizedError, type UpdateEndpointOptions, type WaitForOptions, WebhooksCC, WebhooksCCError, isGitHubWebhook, isLinearWebhook, isPaddleWebhook, isShopifyWebhook, isSlackWebhook, isStripeWebhook, isTwilioWebhook, matchAll, matchAny, matchBodyPath, matchHeader, matchJsonField, matchMethod, parseDuration, parseJsonBody, parseSSE };