@webhooks-cc/sdk 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mads Sauer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * A webhook endpoint that captures incoming HTTP requests.
3
+ * Create endpoints via the dashboard or SDK to receive webhooks.
4
+ */
5
+ interface Endpoint {
6
+ /** Unique identifier for this endpoint */
7
+ id: string;
8
+ /** URL-safe identifier used in webhook URLs (/w/{slug}) */
9
+ slug: string;
10
+ /** Display name for the endpoint */
11
+ name?: string;
12
+ /** Full URL where webhooks should be sent (undefined if server is misconfigured) */
13
+ url?: string;
14
+ /** Unix timestamp (ms) when the endpoint was created */
15
+ createdAt: number;
16
+ }
17
+ /**
18
+ * A captured webhook request with full HTTP details.
19
+ * Stored when a webhook arrives at an endpoint.
20
+ */
21
+ interface Request {
22
+ /** Unique identifier for this request */
23
+ id: string;
24
+ /** Endpoint that received this request */
25
+ endpointId: string;
26
+ /** HTTP method (GET, POST, PUT, etc.) */
27
+ method: string;
28
+ /** Request path after the endpoint slug */
29
+ path: string;
30
+ /** HTTP headers from the original request */
31
+ headers: Record<string, string>;
32
+ /** Request body, if present */
33
+ body?: string;
34
+ /** URL query parameters */
35
+ queryParams: Record<string, string>;
36
+ /** Content-Type header value, if present */
37
+ contentType?: string;
38
+ /** Client IP address */
39
+ ip: string;
40
+ /** Request body size in bytes */
41
+ size: number;
42
+ /** Unix timestamp (ms) when the request arrived */
43
+ receivedAt: number;
44
+ }
45
+ /**
46
+ * Options for creating a new endpoint.
47
+ */
48
+ interface CreateEndpointOptions {
49
+ /** Display name for the endpoint */
50
+ name?: string;
51
+ }
52
+ /**
53
+ * Options for listing captured requests.
54
+ */
55
+ interface ListRequestsOptions {
56
+ /** Maximum number of requests to return */
57
+ limit?: number;
58
+ /** Only return requests received after this timestamp (ms) */
59
+ since?: number;
60
+ }
61
+ /**
62
+ * Options for waitFor() polling behavior.
63
+ */
64
+ 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;
69
+ /** Filter function to match specific requests */
70
+ match?: (request: Request) => boolean;
71
+ }
72
+ /**
73
+ * Configuration options for the WebhooksCC client.
74
+ */
75
+ interface ClientOptions {
76
+ /** API key for authentication (format: whcc_...) */
77
+ apiKey: string;
78
+ /** Base URL for the API (default: https://webhooks.cc) */
79
+ baseUrl?: string;
80
+ /** Request timeout in milliseconds (default: 30000) */
81
+ timeout?: number;
82
+ }
83
+
84
+ /**
85
+ * @fileoverview webhooks.cc SDK client for programmatic webhook management.
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const client = new WebhooksCC({ apiKey: 'whcc_...' });
90
+ * const endpoint = await client.endpoints.create({ name: 'My Webhook' });
91
+ * const request = await client.requests.waitFor(endpoint.slug, {
92
+ * timeout: 10000,
93
+ * match: (r) => r.method === 'POST'
94
+ * });
95
+ * ```
96
+ */
97
+
98
+ /**
99
+ * Error thrown when an API request fails with a specific HTTP status code.
100
+ * Allows callers to distinguish between different error types.
101
+ */
102
+ declare class ApiError extends Error {
103
+ readonly statusCode: number;
104
+ constructor(statusCode: number, message: string);
105
+ }
106
+ /**
107
+ * Client for the webhooks.cc API.
108
+ *
109
+ * Provides methods to create endpoints, list captured requests, and wait
110
+ * for incoming webhooks. Handles authentication, request signing, and
111
+ * response validation.
112
+ */
113
+ declare class WebhooksCC {
114
+ private readonly apiKey;
115
+ private readonly baseUrl;
116
+ private readonly timeout;
117
+ constructor(options: ClientOptions);
118
+ private request;
119
+ endpoints: {
120
+ create: (options?: CreateEndpointOptions) => Promise<Endpoint>;
121
+ list: () => Promise<Endpoint[]>;
122
+ get: (slug: string) => Promise<Endpoint>;
123
+ delete: (slug: string) => Promise<void>;
124
+ };
125
+ requests: {
126
+ list: (endpointSlug: string, options?: ListRequestsOptions) => Promise<Request[]>;
127
+ get: (requestId: string) => Promise<Request>;
128
+ /**
129
+ * Polls for incoming requests until one matches or timeout expires.
130
+ *
131
+ * Fetches requests that arrived since the last successful check. On API
132
+ * errors, continues polling without updating the timestamp to avoid
133
+ * missing requests during transient failures.
134
+ *
135
+ * @param endpointSlug - Endpoint to monitor
136
+ * @param options - Timeout, poll interval, and optional match filter
137
+ * @returns First matching request, or first request if no match filter
138
+ * @throws Error if timeout expires or max iterations (10000) reached
139
+ */
140
+ waitFor: (endpointSlug: string, options?: WaitForOptions) => Promise<Request>;
141
+ };
142
+ }
143
+
144
+ export { ApiError, type ClientOptions, type CreateEndpointOptions, type Endpoint, type ListRequestsOptions, type Request, type WaitForOptions, WebhooksCC };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * A webhook endpoint that captures incoming HTTP requests.
3
+ * Create endpoints via the dashboard or SDK to receive webhooks.
4
+ */
5
+ interface Endpoint {
6
+ /** Unique identifier for this endpoint */
7
+ id: string;
8
+ /** URL-safe identifier used in webhook URLs (/w/{slug}) */
9
+ slug: string;
10
+ /** Display name for the endpoint */
11
+ name?: string;
12
+ /** Full URL where webhooks should be sent (undefined if server is misconfigured) */
13
+ url?: string;
14
+ /** Unix timestamp (ms) when the endpoint was created */
15
+ createdAt: number;
16
+ }
17
+ /**
18
+ * A captured webhook request with full HTTP details.
19
+ * Stored when a webhook arrives at an endpoint.
20
+ */
21
+ interface Request {
22
+ /** Unique identifier for this request */
23
+ id: string;
24
+ /** Endpoint that received this request */
25
+ endpointId: string;
26
+ /** HTTP method (GET, POST, PUT, etc.) */
27
+ method: string;
28
+ /** Request path after the endpoint slug */
29
+ path: string;
30
+ /** HTTP headers from the original request */
31
+ headers: Record<string, string>;
32
+ /** Request body, if present */
33
+ body?: string;
34
+ /** URL query parameters */
35
+ queryParams: Record<string, string>;
36
+ /** Content-Type header value, if present */
37
+ contentType?: string;
38
+ /** Client IP address */
39
+ ip: string;
40
+ /** Request body size in bytes */
41
+ size: number;
42
+ /** Unix timestamp (ms) when the request arrived */
43
+ receivedAt: number;
44
+ }
45
+ /**
46
+ * Options for creating a new endpoint.
47
+ */
48
+ interface CreateEndpointOptions {
49
+ /** Display name for the endpoint */
50
+ name?: string;
51
+ }
52
+ /**
53
+ * Options for listing captured requests.
54
+ */
55
+ interface ListRequestsOptions {
56
+ /** Maximum number of requests to return */
57
+ limit?: number;
58
+ /** Only return requests received after this timestamp (ms) */
59
+ since?: number;
60
+ }
61
+ /**
62
+ * Options for waitFor() polling behavior.
63
+ */
64
+ 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;
69
+ /** Filter function to match specific requests */
70
+ match?: (request: Request) => boolean;
71
+ }
72
+ /**
73
+ * Configuration options for the WebhooksCC client.
74
+ */
75
+ interface ClientOptions {
76
+ /** API key for authentication (format: whcc_...) */
77
+ apiKey: string;
78
+ /** Base URL for the API (default: https://webhooks.cc) */
79
+ baseUrl?: string;
80
+ /** Request timeout in milliseconds (default: 30000) */
81
+ timeout?: number;
82
+ }
83
+
84
+ /**
85
+ * @fileoverview webhooks.cc SDK client for programmatic webhook management.
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const client = new WebhooksCC({ apiKey: 'whcc_...' });
90
+ * const endpoint = await client.endpoints.create({ name: 'My Webhook' });
91
+ * const request = await client.requests.waitFor(endpoint.slug, {
92
+ * timeout: 10000,
93
+ * match: (r) => r.method === 'POST'
94
+ * });
95
+ * ```
96
+ */
97
+
98
+ /**
99
+ * Error thrown when an API request fails with a specific HTTP status code.
100
+ * Allows callers to distinguish between different error types.
101
+ */
102
+ declare class ApiError extends Error {
103
+ readonly statusCode: number;
104
+ constructor(statusCode: number, message: string);
105
+ }
106
+ /**
107
+ * Client for the webhooks.cc API.
108
+ *
109
+ * Provides methods to create endpoints, list captured requests, and wait
110
+ * for incoming webhooks. Handles authentication, request signing, and
111
+ * response validation.
112
+ */
113
+ declare class WebhooksCC {
114
+ private readonly apiKey;
115
+ private readonly baseUrl;
116
+ private readonly timeout;
117
+ constructor(options: ClientOptions);
118
+ private request;
119
+ endpoints: {
120
+ create: (options?: CreateEndpointOptions) => Promise<Endpoint>;
121
+ list: () => Promise<Endpoint[]>;
122
+ get: (slug: string) => Promise<Endpoint>;
123
+ delete: (slug: string) => Promise<void>;
124
+ };
125
+ requests: {
126
+ list: (endpointSlug: string, options?: ListRequestsOptions) => Promise<Request[]>;
127
+ get: (requestId: string) => Promise<Request>;
128
+ /**
129
+ * Polls for incoming requests until one matches or timeout expires.
130
+ *
131
+ * Fetches requests that arrived since the last successful check. On API
132
+ * errors, continues polling without updating the timestamp to avoid
133
+ * missing requests during transient failures.
134
+ *
135
+ * @param endpointSlug - Endpoint to monitor
136
+ * @param options - Timeout, poll interval, and optional match filter
137
+ * @returns First matching request, or first request if no match filter
138
+ * @throws Error if timeout expires or max iterations (10000) reached
139
+ */
140
+ waitFor: (endpointSlug: string, options?: WaitForOptions) => Promise<Request>;
141
+ };
142
+ }
143
+
144
+ export { ApiError, type ClientOptions, type CreateEndpointOptions, type Endpoint, type ListRequestsOptions, type Request, type WaitForOptions, WebhooksCC };
package/dist/index.js ADDED
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ApiError: () => ApiError,
24
+ WebhooksCC: () => WebhooksCC
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/client.ts
29
+ var DEFAULT_BASE_URL = "https://webhooks.cc";
30
+ var DEFAULT_TIMEOUT = 3e4;
31
+ var MIN_POLL_INTERVAL = 10;
32
+ var MAX_POLL_INTERVAL = 6e4;
33
+ var ApiError = class extends Error {
34
+ constructor(statusCode, message) {
35
+ super(`API error (${statusCode}): ${message}`);
36
+ this.statusCode = statusCode;
37
+ this.name = "ApiError";
38
+ }
39
+ };
40
+ var SAFE_PATH_SEGMENT_REGEX = /^[a-zA-Z0-9_-]+$/;
41
+ function validatePathSegment(segment, name) {
42
+ if (!SAFE_PATH_SEGMENT_REGEX.test(segment)) {
43
+ throw new Error(
44
+ `Invalid ${name}: must contain only alphanumeric characters, hyphens, and underscores`
45
+ );
46
+ }
47
+ }
48
+ var WebhooksCC = class {
49
+ constructor(options) {
50
+ this.endpoints = {
51
+ create: async (options = {}) => {
52
+ return this.request("POST", "/endpoints", options);
53
+ },
54
+ list: async () => {
55
+ return this.request("GET", "/endpoints");
56
+ },
57
+ get: async (slug) => {
58
+ validatePathSegment(slug, "slug");
59
+ return this.request("GET", `/endpoints/${slug}`);
60
+ },
61
+ delete: async (slug) => {
62
+ validatePathSegment(slug, "slug");
63
+ await this.request("DELETE", `/endpoints/${slug}`);
64
+ }
65
+ };
66
+ this.requests = {
67
+ list: async (endpointSlug, options = {}) => {
68
+ validatePathSegment(endpointSlug, "endpointSlug");
69
+ const params = new URLSearchParams();
70
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
71
+ if (options.since !== void 0) params.set("since", String(options.since));
72
+ const query = params.toString();
73
+ return this.request(
74
+ "GET",
75
+ `/endpoints/${endpointSlug}/requests${query ? `?${query}` : ""}`
76
+ );
77
+ },
78
+ get: async (requestId) => {
79
+ validatePathSegment(requestId, "requestId");
80
+ return this.request("GET", `/requests/${requestId}`);
81
+ },
82
+ /**
83
+ * Polls for incoming requests until one matches or timeout expires.
84
+ *
85
+ * Fetches requests that arrived since the last successful check. On API
86
+ * errors, continues polling without updating the timestamp to avoid
87
+ * missing requests during transient failures.
88
+ *
89
+ * @param endpointSlug - Endpoint to monitor
90
+ * @param options - Timeout, poll interval, and optional match filter
91
+ * @returns First matching request, or first request if no match filter
92
+ * @throws Error if timeout expires or max iterations (10000) reached
93
+ */
94
+ waitFor: async (endpointSlug, options = {}) => {
95
+ validatePathSegment(endpointSlug, "endpointSlug");
96
+ const { timeout = 3e4, pollInterval = 500, match } = options;
97
+ const safePollInterval = Math.max(
98
+ MIN_POLL_INTERVAL,
99
+ Math.min(MAX_POLL_INTERVAL, pollInterval)
100
+ );
101
+ const start = Date.now();
102
+ let lastChecked = 0;
103
+ const MAX_ITERATIONS = 1e4;
104
+ let iterations = 0;
105
+ while (Date.now() - start < timeout && iterations < MAX_ITERATIONS) {
106
+ iterations++;
107
+ const checkTime = Date.now();
108
+ try {
109
+ const requests = await this.requests.list(endpointSlug, {
110
+ since: lastChecked,
111
+ limit: 100
112
+ });
113
+ lastChecked = checkTime;
114
+ const matched = match ? requests.find(match) : requests[0];
115
+ if (matched) {
116
+ return matched;
117
+ }
118
+ } catch (error) {
119
+ if (error instanceof ApiError) {
120
+ if (error.statusCode === 401) {
121
+ throw new Error("Authentication failed: invalid or expired API key");
122
+ }
123
+ if (error.statusCode === 403) {
124
+ throw new Error("Access denied: insufficient permissions for this endpoint");
125
+ }
126
+ if (error.statusCode === 404) {
127
+ throw new Error(`Endpoint "${endpointSlug}" not found`);
128
+ }
129
+ if (error.statusCode < 500) {
130
+ throw error;
131
+ }
132
+ }
133
+ }
134
+ await sleep(safePollInterval);
135
+ }
136
+ if (iterations >= MAX_ITERATIONS) {
137
+ throw new Error(`Max iterations (${MAX_ITERATIONS}) reached while waiting for request`);
138
+ }
139
+ throw new Error(`Timeout waiting for request after ${timeout}ms`);
140
+ }
141
+ };
142
+ this.apiKey = options.apiKey;
143
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
144
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
145
+ }
146
+ async request(method, path, body) {
147
+ const controller = new AbortController();
148
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
149
+ try {
150
+ const response = await fetch(`${this.baseUrl}/api${path}`, {
151
+ method,
152
+ headers: {
153
+ Authorization: `Bearer ${this.apiKey}`,
154
+ "Content-Type": "application/json"
155
+ },
156
+ body: body ? JSON.stringify(body) : void 0,
157
+ signal: controller.signal
158
+ });
159
+ if (!response.ok) {
160
+ const error = await response.text();
161
+ const sanitizedError = error.length > 200 ? error.slice(0, 200) + "..." : error;
162
+ throw new ApiError(response.status, sanitizedError);
163
+ }
164
+ if (response.status === 204 || response.headers.get("content-length") === "0") {
165
+ return void 0;
166
+ }
167
+ const contentType = response.headers.get("content-type");
168
+ if (contentType && !contentType.includes("application/json")) {
169
+ throw new Error(`Unexpected content type: ${contentType}`);
170
+ }
171
+ return response.json();
172
+ } catch (error) {
173
+ if (error instanceof Error && error.name === "AbortError") {
174
+ throw new Error(`Request timed out after ${this.timeout}ms`);
175
+ }
176
+ throw error;
177
+ } finally {
178
+ clearTimeout(timeoutId);
179
+ }
180
+ }
181
+ };
182
+ function sleep(ms) {
183
+ return new Promise((resolve) => setTimeout(resolve, ms));
184
+ }
185
+ // Annotate the CommonJS export names for ESM import in node:
186
+ 0 && (module.exports = {
187
+ ApiError,
188
+ WebhooksCC
189
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,161 @@
1
+ // src/client.ts
2
+ var DEFAULT_BASE_URL = "https://webhooks.cc";
3
+ var DEFAULT_TIMEOUT = 3e4;
4
+ var MIN_POLL_INTERVAL = 10;
5
+ var MAX_POLL_INTERVAL = 6e4;
6
+ var ApiError = class extends Error {
7
+ constructor(statusCode, message) {
8
+ super(`API error (${statusCode}): ${message}`);
9
+ this.statusCode = statusCode;
10
+ this.name = "ApiError";
11
+ }
12
+ };
13
+ var SAFE_PATH_SEGMENT_REGEX = /^[a-zA-Z0-9_-]+$/;
14
+ function validatePathSegment(segment, name) {
15
+ if (!SAFE_PATH_SEGMENT_REGEX.test(segment)) {
16
+ throw new Error(
17
+ `Invalid ${name}: must contain only alphanumeric characters, hyphens, and underscores`
18
+ );
19
+ }
20
+ }
21
+ var WebhooksCC = class {
22
+ constructor(options) {
23
+ this.endpoints = {
24
+ create: async (options = {}) => {
25
+ return this.request("POST", "/endpoints", options);
26
+ },
27
+ list: async () => {
28
+ return this.request("GET", "/endpoints");
29
+ },
30
+ get: async (slug) => {
31
+ validatePathSegment(slug, "slug");
32
+ return this.request("GET", `/endpoints/${slug}`);
33
+ },
34
+ delete: async (slug) => {
35
+ validatePathSegment(slug, "slug");
36
+ await this.request("DELETE", `/endpoints/${slug}`);
37
+ }
38
+ };
39
+ this.requests = {
40
+ list: async (endpointSlug, options = {}) => {
41
+ validatePathSegment(endpointSlug, "endpointSlug");
42
+ const params = new URLSearchParams();
43
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
44
+ if (options.since !== void 0) params.set("since", String(options.since));
45
+ const query = params.toString();
46
+ return this.request(
47
+ "GET",
48
+ `/endpoints/${endpointSlug}/requests${query ? `?${query}` : ""}`
49
+ );
50
+ },
51
+ get: async (requestId) => {
52
+ validatePathSegment(requestId, "requestId");
53
+ return this.request("GET", `/requests/${requestId}`);
54
+ },
55
+ /**
56
+ * Polls for incoming requests until one matches or timeout expires.
57
+ *
58
+ * Fetches requests that arrived since the last successful check. On API
59
+ * errors, continues polling without updating the timestamp to avoid
60
+ * missing requests during transient failures.
61
+ *
62
+ * @param endpointSlug - Endpoint to monitor
63
+ * @param options - Timeout, poll interval, and optional match filter
64
+ * @returns First matching request, or first request if no match filter
65
+ * @throws Error if timeout expires or max iterations (10000) reached
66
+ */
67
+ waitFor: async (endpointSlug, options = {}) => {
68
+ validatePathSegment(endpointSlug, "endpointSlug");
69
+ const { timeout = 3e4, pollInterval = 500, match } = options;
70
+ const safePollInterval = Math.max(
71
+ MIN_POLL_INTERVAL,
72
+ Math.min(MAX_POLL_INTERVAL, pollInterval)
73
+ );
74
+ const start = Date.now();
75
+ let lastChecked = 0;
76
+ const MAX_ITERATIONS = 1e4;
77
+ let iterations = 0;
78
+ while (Date.now() - start < timeout && iterations < MAX_ITERATIONS) {
79
+ iterations++;
80
+ const checkTime = Date.now();
81
+ try {
82
+ const requests = await this.requests.list(endpointSlug, {
83
+ since: lastChecked,
84
+ limit: 100
85
+ });
86
+ lastChecked = checkTime;
87
+ const matched = match ? requests.find(match) : requests[0];
88
+ if (matched) {
89
+ return matched;
90
+ }
91
+ } catch (error) {
92
+ if (error instanceof ApiError) {
93
+ if (error.statusCode === 401) {
94
+ throw new Error("Authentication failed: invalid or expired API key");
95
+ }
96
+ if (error.statusCode === 403) {
97
+ throw new Error("Access denied: insufficient permissions for this endpoint");
98
+ }
99
+ if (error.statusCode === 404) {
100
+ throw new Error(`Endpoint "${endpointSlug}" not found`);
101
+ }
102
+ if (error.statusCode < 500) {
103
+ throw error;
104
+ }
105
+ }
106
+ }
107
+ await sleep(safePollInterval);
108
+ }
109
+ if (iterations >= MAX_ITERATIONS) {
110
+ throw new Error(`Max iterations (${MAX_ITERATIONS}) reached while waiting for request`);
111
+ }
112
+ throw new Error(`Timeout waiting for request after ${timeout}ms`);
113
+ }
114
+ };
115
+ this.apiKey = options.apiKey;
116
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
117
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
118
+ }
119
+ async request(method, path, body) {
120
+ const controller = new AbortController();
121
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
122
+ try {
123
+ const response = await fetch(`${this.baseUrl}/api${path}`, {
124
+ method,
125
+ headers: {
126
+ Authorization: `Bearer ${this.apiKey}`,
127
+ "Content-Type": "application/json"
128
+ },
129
+ body: body ? JSON.stringify(body) : void 0,
130
+ signal: controller.signal
131
+ });
132
+ if (!response.ok) {
133
+ const error = await response.text();
134
+ const sanitizedError = error.length > 200 ? error.slice(0, 200) + "..." : error;
135
+ throw new ApiError(response.status, sanitizedError);
136
+ }
137
+ if (response.status === 204 || response.headers.get("content-length") === "0") {
138
+ return void 0;
139
+ }
140
+ const contentType = response.headers.get("content-type");
141
+ if (contentType && !contentType.includes("application/json")) {
142
+ throw new Error(`Unexpected content type: ${contentType}`);
143
+ }
144
+ return response.json();
145
+ } catch (error) {
146
+ if (error instanceof Error && error.name === "AbortError") {
147
+ throw new Error(`Request timed out after ${this.timeout}ms`);
148
+ }
149
+ throw error;
150
+ } finally {
151
+ clearTimeout(timeoutId);
152
+ }
153
+ }
154
+ };
155
+ function sleep(ms) {
156
+ return new Promise((resolve) => setTimeout(resolve, ms));
157
+ }
158
+ export {
159
+ ApiError,
160
+ WebhooksCC
161
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@webhooks-cc/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official SDK for webhooks.cc — programmatic webhook testing",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "webhook",
20
+ "testing",
21
+ "api",
22
+ "sdk",
23
+ "webhooks-cc"
24
+ ],
25
+ "author": "",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/webhooks-cc/webhooks-cc",
30
+ "directory": "packages/sdk"
31
+ },
32
+ "homepage": "https://webhooks.cc",
33
+ "devDependencies": {
34
+ "tsup": "^8.5.1",
35
+ "typescript": "^5.9.3",
36
+ "vitest": "^3.0.0"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup src/index.ts --format cjs,esm --dts",
40
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
41
+ "typecheck": "tsc --noEmit",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest"
44
+ }
45
+ }