@webhooks-cc/sdk 0.5.0 → 0.6.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/dist/index.d.mts CHANGED
@@ -199,6 +199,7 @@ interface SDKDescription {
199
199
  version: string;
200
200
  endpoints: Record<string, OperationDescription>;
201
201
  sendTo: OperationDescription;
202
+ buildRequest: OperationDescription;
202
203
  requests: Record<string, OperationDescription>;
203
204
  }
204
205
 
@@ -275,6 +276,21 @@ declare class WebhooksCC {
275
276
  send: (slug: string, options?: SendOptions) => Promise<Response>;
276
277
  sendTemplate: (slug: string, options: SendTemplateOptions) => Promise<Response>;
277
278
  };
279
+ /**
280
+ * Build a request without sending it. Returns the computed method, URL,
281
+ * headers, and body — including any provider signatures. Useful for
282
+ * debugging what sendTo would actually send.
283
+ *
284
+ * @param url - Target URL (http or https)
285
+ * @param options - Same options as sendTo
286
+ * @returns The computed request details
287
+ */
288
+ buildRequest: (url: string, options?: SendToOptions) => Promise<{
289
+ url: string;
290
+ method: string;
291
+ headers: Record<string, string>;
292
+ body?: string;
293
+ }>;
278
294
  /**
279
295
  * Send a webhook directly to any URL with optional provider signing.
280
296
  * Use this for local integration testing — send properly signed webhooks
package/dist/index.d.ts CHANGED
@@ -199,6 +199,7 @@ interface SDKDescription {
199
199
  version: string;
200
200
  endpoints: Record<string, OperationDescription>;
201
201
  sendTo: OperationDescription;
202
+ buildRequest: OperationDescription;
202
203
  requests: Record<string, OperationDescription>;
203
204
  }
204
205
 
@@ -275,6 +276,21 @@ declare class WebhooksCC {
275
276
  send: (slug: string, options?: SendOptions) => Promise<Response>;
276
277
  sendTemplate: (slug: string, options: SendTemplateOptions) => Promise<Response>;
277
278
  };
279
+ /**
280
+ * Build a request without sending it. Returns the computed method, URL,
281
+ * headers, and body — including any provider signatures. Useful for
282
+ * debugging what sendTo would actually send.
283
+ *
284
+ * @param url - Target URL (http or https)
285
+ * @param options - Same options as sendTo
286
+ * @returns The computed request details
287
+ */
288
+ buildRequest: (url: string, options?: SendToOptions) => Promise<{
289
+ url: string;
290
+ method: string;
291
+ headers: Record<string, string>;
292
+ body?: string;
293
+ }>;
278
294
  /**
279
295
  * Send a webhook directly to any URL with optional provider signing.
280
296
  * Use this for local integration testing — send properly signed webhooks
package/dist/index.js CHANGED
@@ -768,10 +768,17 @@ async function buildTemplateSendOptions(endpointUrl, options) {
768
768
  const timestamp = options.timestamp ?? Math.floor(Date.now() / 1e3);
769
769
  const signingInput = `${msgId}.${timestamp}.${body}`;
770
770
  let rawSecret = options.secret;
771
- if (rawSecret.startsWith("whsec_")) {
771
+ const hadPrefix = rawSecret.startsWith("whsec_");
772
+ if (hadPrefix) {
772
773
  rawSecret = rawSecret.slice(6);
773
774
  }
774
- const secretBytes = fromBase64(rawSecret);
775
+ let secretBytes;
776
+ try {
777
+ secretBytes = fromBase64(rawSecret);
778
+ } catch {
779
+ const raw = hadPrefix ? options.secret : rawSecret;
780
+ secretBytes = new TextEncoder().encode(raw);
781
+ }
775
782
  const signature = await hmacSignRaw("SHA-256", secretBytes, signingInput);
776
783
  return {
777
784
  method: method2,
@@ -833,7 +840,7 @@ async function buildTemplateSendOptions(endpointUrl, options) {
833
840
  var DEFAULT_BASE_URL = "https://webhooks.cc";
834
841
  var DEFAULT_WEBHOOK_URL = "https://go.webhooks.cc";
835
842
  var DEFAULT_TIMEOUT = 3e4;
836
- var SDK_VERSION = "0.5.0";
843
+ var SDK_VERSION = "0.6.0";
837
844
  var MIN_POLL_INTERVAL = 10;
838
845
  var MAX_POLL_INTERVAL = 6e4;
839
846
  var ALLOWED_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]);
@@ -956,6 +963,64 @@ var WebhooksCC = class {
956
963
  return this.endpoints.send(slug, sendOptions);
957
964
  }
958
965
  };
966
+ /**
967
+ * Build a request without sending it. Returns the computed method, URL,
968
+ * headers, and body — including any provider signatures. Useful for
969
+ * debugging what sendTo would actually send.
970
+ *
971
+ * @param url - Target URL (http or https)
972
+ * @param options - Same options as sendTo
973
+ * @returns The computed request details
974
+ */
975
+ this.buildRequest = async (url, options = {}) => {
976
+ let parsed;
977
+ try {
978
+ parsed = new URL(url);
979
+ } catch {
980
+ throw new Error(`Invalid URL: "${url}" is not a valid URL`);
981
+ }
982
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
983
+ throw new Error("Invalid URL: only http and https protocols are supported");
984
+ }
985
+ if (options.provider) {
986
+ if (!options.secret || typeof options.secret !== "string") {
987
+ throw new Error("buildRequest with a provider requires a non-empty secret");
988
+ }
989
+ const sendOptions = await buildTemplateSendOptions(url, {
990
+ provider: options.provider,
991
+ template: options.template,
992
+ secret: options.secret,
993
+ event: options.event,
994
+ body: options.body,
995
+ method: options.method,
996
+ headers: options.headers,
997
+ timestamp: options.timestamp
998
+ });
999
+ return {
1000
+ url,
1001
+ method: (sendOptions.method ?? "POST").toUpperCase(),
1002
+ headers: sendOptions.headers ?? {},
1003
+ body: sendOptions.body !== void 0 ? typeof sendOptions.body === "string" ? sendOptions.body : JSON.stringify(sendOptions.body) : void 0
1004
+ };
1005
+ }
1006
+ const method = (options.method ?? "POST").toUpperCase();
1007
+ if (!ALLOWED_METHODS.has(method)) {
1008
+ throw new Error(
1009
+ `Invalid HTTP method: "${options.method}". Must be one of: ${[...ALLOWED_METHODS].join(", ")}`
1010
+ );
1011
+ }
1012
+ const headers = { ...options.headers ?? {} };
1013
+ const hasContentType = Object.keys(headers).some((k) => k.toLowerCase() === "content-type");
1014
+ if (options.body !== void 0 && !hasContentType) {
1015
+ headers["Content-Type"] = "application/json";
1016
+ }
1017
+ return {
1018
+ url,
1019
+ method,
1020
+ headers,
1021
+ body: options.body !== void 0 ? typeof options.body === "string" ? options.body : JSON.stringify(options.body) : void 0
1022
+ };
1023
+ };
959
1024
  /**
960
1025
  * Send a webhook directly to any URL with optional provider signing.
961
1026
  * Use this for local integration testing — send properly signed webhooks
@@ -1380,6 +1445,16 @@ var WebhooksCC = class {
1380
1445
  headers: "Record<string, string>?"
1381
1446
  }
1382
1447
  },
1448
+ buildRequest: {
1449
+ description: "Build a request without sending it \u2014 returns computed method, URL, headers, and body including provider signatures",
1450
+ params: {
1451
+ url: "string",
1452
+ provider: '"stripe"|"github"|"shopify"|"twilio"|"standard-webhooks"?',
1453
+ secret: "string?",
1454
+ body: "unknown?",
1455
+ headers: "Record<string, string>?"
1456
+ }
1457
+ },
1383
1458
  requests: {
1384
1459
  list: {
1385
1460
  description: "List captured requests",
package/dist/index.mjs CHANGED
@@ -719,10 +719,17 @@ async function buildTemplateSendOptions(endpointUrl, options) {
719
719
  const timestamp = options.timestamp ?? Math.floor(Date.now() / 1e3);
720
720
  const signingInput = `${msgId}.${timestamp}.${body}`;
721
721
  let rawSecret = options.secret;
722
- if (rawSecret.startsWith("whsec_")) {
722
+ const hadPrefix = rawSecret.startsWith("whsec_");
723
+ if (hadPrefix) {
723
724
  rawSecret = rawSecret.slice(6);
724
725
  }
725
- const secretBytes = fromBase64(rawSecret);
726
+ let secretBytes;
727
+ try {
728
+ secretBytes = fromBase64(rawSecret);
729
+ } catch {
730
+ const raw = hadPrefix ? options.secret : rawSecret;
731
+ secretBytes = new TextEncoder().encode(raw);
732
+ }
726
733
  const signature = await hmacSignRaw("SHA-256", secretBytes, signingInput);
727
734
  return {
728
735
  method: method2,
@@ -784,7 +791,7 @@ async function buildTemplateSendOptions(endpointUrl, options) {
784
791
  var DEFAULT_BASE_URL = "https://webhooks.cc";
785
792
  var DEFAULT_WEBHOOK_URL = "https://go.webhooks.cc";
786
793
  var DEFAULT_TIMEOUT = 3e4;
787
- var SDK_VERSION = "0.5.0";
794
+ var SDK_VERSION = "0.6.0";
788
795
  var MIN_POLL_INTERVAL = 10;
789
796
  var MAX_POLL_INTERVAL = 6e4;
790
797
  var ALLOWED_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]);
@@ -907,6 +914,64 @@ var WebhooksCC = class {
907
914
  return this.endpoints.send(slug, sendOptions);
908
915
  }
909
916
  };
917
+ /**
918
+ * Build a request without sending it. Returns the computed method, URL,
919
+ * headers, and body — including any provider signatures. Useful for
920
+ * debugging what sendTo would actually send.
921
+ *
922
+ * @param url - Target URL (http or https)
923
+ * @param options - Same options as sendTo
924
+ * @returns The computed request details
925
+ */
926
+ this.buildRequest = async (url, options = {}) => {
927
+ let parsed;
928
+ try {
929
+ parsed = new URL(url);
930
+ } catch {
931
+ throw new Error(`Invalid URL: "${url}" is not a valid URL`);
932
+ }
933
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
934
+ throw new Error("Invalid URL: only http and https protocols are supported");
935
+ }
936
+ if (options.provider) {
937
+ if (!options.secret || typeof options.secret !== "string") {
938
+ throw new Error("buildRequest with a provider requires a non-empty secret");
939
+ }
940
+ const sendOptions = await buildTemplateSendOptions(url, {
941
+ provider: options.provider,
942
+ template: options.template,
943
+ secret: options.secret,
944
+ event: options.event,
945
+ body: options.body,
946
+ method: options.method,
947
+ headers: options.headers,
948
+ timestamp: options.timestamp
949
+ });
950
+ return {
951
+ url,
952
+ method: (sendOptions.method ?? "POST").toUpperCase(),
953
+ headers: sendOptions.headers ?? {},
954
+ body: sendOptions.body !== void 0 ? typeof sendOptions.body === "string" ? sendOptions.body : JSON.stringify(sendOptions.body) : void 0
955
+ };
956
+ }
957
+ const method = (options.method ?? "POST").toUpperCase();
958
+ if (!ALLOWED_METHODS.has(method)) {
959
+ throw new Error(
960
+ `Invalid HTTP method: "${options.method}". Must be one of: ${[...ALLOWED_METHODS].join(", ")}`
961
+ );
962
+ }
963
+ const headers = { ...options.headers ?? {} };
964
+ const hasContentType = Object.keys(headers).some((k) => k.toLowerCase() === "content-type");
965
+ if (options.body !== void 0 && !hasContentType) {
966
+ headers["Content-Type"] = "application/json";
967
+ }
968
+ return {
969
+ url,
970
+ method,
971
+ headers,
972
+ body: options.body !== void 0 ? typeof options.body === "string" ? options.body : JSON.stringify(options.body) : void 0
973
+ };
974
+ };
910
975
  /**
911
976
  * Send a webhook directly to any URL with optional provider signing.
912
977
  * Use this for local integration testing — send properly signed webhooks
@@ -1331,6 +1396,16 @@ var WebhooksCC = class {
1331
1396
  headers: "Record<string, string>?"
1332
1397
  }
1333
1398
  },
1399
+ buildRequest: {
1400
+ description: "Build a request without sending it \u2014 returns computed method, URL, headers, and body including provider signatures",
1401
+ params: {
1402
+ url: "string",
1403
+ provider: '"stripe"|"github"|"shopify"|"twilio"|"standard-webhooks"?',
1404
+ secret: "string?",
1405
+ body: "unknown?",
1406
+ headers: "Record<string, string>?"
1407
+ }
1408
+ },
1334
1409
  requests: {
1335
1410
  list: {
1336
1411
  description: "List captured requests",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webhooks-cc/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "TypeScript SDK for webhooks.cc — create endpoints, capture requests, assert in tests",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",