@webhooks-cc/sdk 0.3.0 → 0.4.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.mjs CHANGED
@@ -145,6 +145,588 @@ async function* parseSSE(stream) {
145
145
  }
146
146
  }
147
147
 
148
+ // src/templates.ts
149
+ var DEFAULT_TEMPLATE_BY_PROVIDER = {
150
+ stripe: "payment_intent.succeeded",
151
+ github: "push",
152
+ shopify: "orders/create",
153
+ twilio: "messaging.inbound"
154
+ };
155
+ var PROVIDER_TEMPLATES = {
156
+ stripe: ["payment_intent.succeeded", "checkout.session.completed", "invoice.paid"],
157
+ github: ["push", "pull_request.opened", "ping"],
158
+ shopify: ["orders/create", "orders/paid", "products/update", "app/uninstalled"],
159
+ twilio: ["messaging.inbound", "messaging.status_callback", "voice.incoming_call"]
160
+ };
161
+ function randomHex(length) {
162
+ const bytes = new Uint8Array(Math.ceil(length / 2));
163
+ globalThis.crypto.getRandomValues(bytes);
164
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
165
+ }
166
+ function randomToken(prefix) {
167
+ return `${prefix}_${randomHex(8)}`;
168
+ }
169
+ function randomDigits(length) {
170
+ const bytes = new Uint8Array(length);
171
+ globalThis.crypto.getRandomValues(bytes);
172
+ return Array.from(bytes, (b) => (b % 10).toString()).join("");
173
+ }
174
+ function randomSid(prefix) {
175
+ return `${prefix}${randomHex(32)}`;
176
+ }
177
+ function randomUuid() {
178
+ if (typeof globalThis.crypto?.randomUUID === "function") {
179
+ return globalThis.crypto.randomUUID();
180
+ }
181
+ return `${randomHex(8)}-${randomHex(4)}-${randomHex(4)}-${randomHex(4)}-${randomHex(12)}`;
182
+ }
183
+ function repositoryPayload() {
184
+ return {
185
+ id: Number(randomDigits(9)),
186
+ name: "demo-repo",
187
+ full_name: "webhooks-cc/demo-repo",
188
+ private: false,
189
+ default_branch: "main",
190
+ html_url: "https://github.com/webhooks-cc/demo-repo"
191
+ };
192
+ }
193
+ function githubSender() {
194
+ return {
195
+ login: "webhooks-cc-bot",
196
+ id: 987654,
197
+ type: "Bot"
198
+ };
199
+ }
200
+ function ensureTemplate(provider, template) {
201
+ const resolved = template ?? DEFAULT_TEMPLATE_BY_PROVIDER[provider];
202
+ const supported = PROVIDER_TEMPLATES[provider];
203
+ if (!supported.some((item) => item === resolved)) {
204
+ throw new Error(
205
+ `Unsupported template "${resolved}" for provider "${provider}". Supported templates: ${supported.join(", ")}`
206
+ );
207
+ }
208
+ return resolved;
209
+ }
210
+ function defaultEvent(provider, template) {
211
+ if (provider === "github" && template === "pull_request.opened") {
212
+ return "pull_request";
213
+ }
214
+ return template;
215
+ }
216
+ function formEncode(params) {
217
+ const form = new URLSearchParams();
218
+ for (const [key, value] of Object.entries(params)) {
219
+ form.append(key, value);
220
+ }
221
+ return form.toString();
222
+ }
223
+ function asStringRecord(value) {
224
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
225
+ return null;
226
+ }
227
+ const out = {};
228
+ for (const [k, v] of Object.entries(value)) {
229
+ if (v == null) {
230
+ out[k] = "";
231
+ continue;
232
+ }
233
+ out[k] = typeof v === "string" ? v : String(v);
234
+ }
235
+ return out;
236
+ }
237
+ function buildTemplatePayload(provider, template, event, now, bodyOverride) {
238
+ const nowSec = Math.floor(now.getTime() / 1e3);
239
+ const nowIso = now.toISOString();
240
+ if (provider === "stripe") {
241
+ const paymentIntentId = randomToken("pi");
242
+ const checkoutSessionId = `cs_test_${randomHex(24)}`;
243
+ const payloadByTemplate = {
244
+ "payment_intent.succeeded": {
245
+ id: randomToken("evt"),
246
+ object: "event",
247
+ api_version: "2025-01-27.acacia",
248
+ created: nowSec,
249
+ data: {
250
+ object: {
251
+ id: paymentIntentId,
252
+ object: "payment_intent",
253
+ amount: 2e3,
254
+ amount_received: 2e3,
255
+ currency: "usd",
256
+ status: "succeeded",
257
+ created: nowSec,
258
+ metadata: {
259
+ order_id: randomToken("order")
260
+ }
261
+ }
262
+ },
263
+ livemode: false,
264
+ pending_webhooks: 1,
265
+ request: {
266
+ id: `req_${randomHex(24)}`,
267
+ idempotency_key: null
268
+ },
269
+ type: event
270
+ },
271
+ "checkout.session.completed": {
272
+ id: randomToken("evt"),
273
+ object: "event",
274
+ api_version: "2025-01-27.acacia",
275
+ created: nowSec,
276
+ data: {
277
+ object: {
278
+ id: checkoutSessionId,
279
+ object: "checkout.session",
280
+ mode: "payment",
281
+ payment_status: "paid",
282
+ amount_total: 2e3,
283
+ amount_subtotal: 2e3,
284
+ currency: "usd",
285
+ customer: `cus_${randomHex(14)}`,
286
+ payment_intent: paymentIntentId,
287
+ status: "complete",
288
+ success_url: "https://example.com/success",
289
+ cancel_url: "https://example.com/cancel",
290
+ created: nowSec
291
+ }
292
+ },
293
+ livemode: false,
294
+ pending_webhooks: 1,
295
+ request: {
296
+ id: `req_${randomHex(24)}`,
297
+ idempotency_key: null
298
+ },
299
+ type: event
300
+ },
301
+ "invoice.paid": {
302
+ id: randomToken("evt"),
303
+ object: "event",
304
+ api_version: "2025-01-27.acacia",
305
+ created: nowSec,
306
+ data: {
307
+ object: {
308
+ id: `in_${randomHex(14)}`,
309
+ object: "invoice",
310
+ account_country: "US",
311
+ account_name: "webhooks.cc demo",
312
+ amount_due: 2e3,
313
+ amount_paid: 2e3,
314
+ amount_remaining: 0,
315
+ billing_reason: "subscription_cycle",
316
+ currency: "usd",
317
+ customer: `cus_${randomHex(14)}`,
318
+ paid: true,
319
+ status: "paid",
320
+ hosted_invoice_url: "https://invoice.stripe.com/demo",
321
+ created: nowSec
322
+ }
323
+ },
324
+ livemode: false,
325
+ pending_webhooks: 1,
326
+ request: {
327
+ id: `req_${randomHex(24)}`,
328
+ idempotency_key: null
329
+ },
330
+ type: event
331
+ }
332
+ };
333
+ const payload = bodyOverride ?? payloadByTemplate[template];
334
+ const body = typeof payload === "string" ? payload : JSON.stringify(payload);
335
+ return {
336
+ body,
337
+ contentType: "application/json",
338
+ headers: {
339
+ "user-agent": "Stripe/1.0 (+https://stripe.com/docs/webhooks)"
340
+ }
341
+ };
342
+ }
343
+ if (provider === "github") {
344
+ const before = randomHex(40);
345
+ const after = randomHex(40);
346
+ const baseRepo = repositoryPayload();
347
+ const payloadByTemplate = {
348
+ push: {
349
+ ref: "refs/heads/main",
350
+ before,
351
+ after,
352
+ repository: baseRepo,
353
+ pusher: {
354
+ name: "webhooks-cc-bot",
355
+ email: "bot@webhooks.cc"
356
+ },
357
+ sender: githubSender(),
358
+ created: false,
359
+ deleted: false,
360
+ forced: false,
361
+ compare: `https://github.com/${baseRepo.full_name}/compare/${before}...${after}`,
362
+ commits: [
363
+ {
364
+ id: after,
365
+ message: "Update webhook integration tests",
366
+ timestamp: nowIso,
367
+ url: `https://github.com/${baseRepo.full_name}/commit/${after}`,
368
+ author: {
369
+ name: "webhooks-cc-bot",
370
+ email: "bot@webhooks.cc"
371
+ },
372
+ committer: {
373
+ name: "webhooks-cc-bot",
374
+ email: "bot@webhooks.cc"
375
+ },
376
+ added: [],
377
+ removed: [],
378
+ modified: ["src/webhooks.ts"]
379
+ }
380
+ ],
381
+ head_commit: {
382
+ id: after,
383
+ message: "Update webhook integration tests",
384
+ timestamp: nowIso,
385
+ url: `https://github.com/${baseRepo.full_name}/commit/${after}`,
386
+ author: {
387
+ name: "webhooks-cc-bot",
388
+ email: "bot@webhooks.cc"
389
+ },
390
+ committer: {
391
+ name: "webhooks-cc-bot",
392
+ email: "bot@webhooks.cc"
393
+ },
394
+ added: [],
395
+ removed: [],
396
+ modified: ["src/webhooks.ts"]
397
+ }
398
+ },
399
+ "pull_request.opened": {
400
+ action: "opened",
401
+ number: 42,
402
+ pull_request: {
403
+ id: Number(randomDigits(9)),
404
+ number: 42,
405
+ state: "open",
406
+ title: "Add webhook retry logic",
407
+ body: "This PR improves retry handling for inbound webhooks.",
408
+ created_at: nowIso,
409
+ updated_at: nowIso,
410
+ html_url: `https://github.com/${baseRepo.full_name}/pull/42`,
411
+ user: {
412
+ login: "webhooks-cc-bot",
413
+ id: 987654,
414
+ type: "Bot"
415
+ },
416
+ draft: false,
417
+ head: {
418
+ label: "webhooks-cc:feature/webhook-retries",
419
+ ref: "feature/webhook-retries",
420
+ sha: randomHex(40),
421
+ repo: baseRepo
422
+ },
423
+ base: {
424
+ label: "webhooks-cc:main",
425
+ ref: "main",
426
+ sha: randomHex(40),
427
+ repo: baseRepo
428
+ }
429
+ },
430
+ repository: baseRepo,
431
+ sender: githubSender()
432
+ },
433
+ ping: {
434
+ zen: "Keep it logically awesome.",
435
+ hook_id: Number(randomDigits(7)),
436
+ hook: {
437
+ type: "Repository",
438
+ id: Number(randomDigits(7)),
439
+ name: "web",
440
+ active: true,
441
+ events: ["push", "pull_request"],
442
+ config: {
443
+ content_type: "json",
444
+ insecure_ssl: "0",
445
+ url: "https://go.webhooks.cc/w/demo"
446
+ }
447
+ },
448
+ repository: baseRepo,
449
+ sender: githubSender()
450
+ }
451
+ };
452
+ const payload = bodyOverride ?? payloadByTemplate[template];
453
+ const body = typeof payload === "string" ? payload : JSON.stringify(payload);
454
+ return {
455
+ body,
456
+ contentType: "application/json",
457
+ headers: {
458
+ "user-agent": "GitHub-Hookshot/8f03f6d"
459
+ }
460
+ };
461
+ }
462
+ if (provider === "shopify") {
463
+ const payloadByTemplate = {
464
+ "orders/create": {
465
+ id: Number(randomDigits(10)),
466
+ admin_graphql_api_id: `gid://shopify/Order/${randomDigits(10)}`,
467
+ email: "customer@example.com",
468
+ created_at: nowIso,
469
+ updated_at: nowIso,
470
+ currency: "USD",
471
+ financial_status: "pending",
472
+ fulfillment_status: null,
473
+ total_price: "19.99",
474
+ subtotal_price: "19.99",
475
+ total_tax: "0.00",
476
+ line_items: [
477
+ {
478
+ id: Number(randomDigits(10)),
479
+ admin_graphql_api_id: `gid://shopify/LineItem/${randomDigits(10)}`,
480
+ title: "Demo Item",
481
+ quantity: 1,
482
+ sku: "DEMO-001",
483
+ price: "19.99"
484
+ }
485
+ ]
486
+ },
487
+ "orders/paid": {
488
+ id: Number(randomDigits(10)),
489
+ admin_graphql_api_id: `gid://shopify/Order/${randomDigits(10)}`,
490
+ email: "customer@example.com",
491
+ created_at: nowIso,
492
+ updated_at: nowIso,
493
+ currency: "USD",
494
+ financial_status: "paid",
495
+ fulfillment_status: null,
496
+ total_price: "49.00",
497
+ subtotal_price: "49.00",
498
+ total_tax: "0.00",
499
+ line_items: [
500
+ {
501
+ id: Number(randomDigits(10)),
502
+ admin_graphql_api_id: `gid://shopify/LineItem/${randomDigits(10)}`,
503
+ title: "Webhook Pro Plan",
504
+ quantity: 1,
505
+ sku: "WHK-PRO",
506
+ price: "49.00"
507
+ }
508
+ ]
509
+ },
510
+ "products/update": {
511
+ id: Number(randomDigits(10)),
512
+ admin_graphql_api_id: `gid://shopify/Product/${randomDigits(10)}`,
513
+ title: "Webhook Tester Hoodie",
514
+ body_html: "<strong>Updated product details</strong>",
515
+ vendor: "webhooks.cc",
516
+ product_type: "Apparel",
517
+ handle: "webhook-tester-hoodie",
518
+ status: "active",
519
+ created_at: nowIso,
520
+ updated_at: nowIso,
521
+ variants: [
522
+ {
523
+ id: Number(randomDigits(10)),
524
+ product_id: Number(randomDigits(10)),
525
+ title: "Default Title",
526
+ price: "39.00",
527
+ sku: "WHK-HOODIE",
528
+ position: 1,
529
+ inventory_policy: "deny",
530
+ fulfillment_service: "manual",
531
+ inventory_management: "shopify"
532
+ }
533
+ ]
534
+ },
535
+ "app/uninstalled": {
536
+ id: Number(randomDigits(10)),
537
+ name: "Demo Shop",
538
+ email: "owner@example.com",
539
+ domain: "demo-shop.myshopify.com",
540
+ myshopify_domain: "demo-shop.myshopify.com",
541
+ country_name: "United States",
542
+ currency: "USD",
543
+ plan_name: "basic",
544
+ created_at: nowIso,
545
+ updated_at: nowIso
546
+ }
547
+ };
548
+ const payload = bodyOverride ?? payloadByTemplate[template];
549
+ const body = typeof payload === "string" ? payload : JSON.stringify(payload);
550
+ return {
551
+ body,
552
+ contentType: "application/json",
553
+ headers: {
554
+ "x-shopify-shop-domain": "demo-shop.myshopify.com",
555
+ "x-shopify-api-version": "2025-10",
556
+ "x-shopify-webhook-id": randomUuid(),
557
+ "x-shopify-event-id": randomUuid(),
558
+ "x-shopify-triggered-at": nowIso
559
+ }
560
+ };
561
+ }
562
+ if (provider !== "twilio") {
563
+ throw new Error(`Unsupported provider: ${provider}`);
564
+ }
565
+ const defaultTwilioParamsByTemplate = {
566
+ "messaging.inbound": {
567
+ AccountSid: randomSid("AC"),
568
+ ApiVersion: "2010-04-01",
569
+ MessageSid: randomSid("SM"),
570
+ SmsSid: randomSid("SM"),
571
+ SmsMessageSid: randomSid("SM"),
572
+ From: "+14155550123",
573
+ To: "+14155559876",
574
+ Body: "Hello from webhooks.cc",
575
+ NumMedia: "0",
576
+ NumSegments: "1",
577
+ MessageStatus: "received",
578
+ SmsStatus: "received",
579
+ FromCity: "SAN FRANCISCO",
580
+ FromState: "CA",
581
+ FromCountry: "US",
582
+ FromZip: "94105",
583
+ ToCity: "",
584
+ ToState: "",
585
+ ToCountry: "US",
586
+ ToZip: ""
587
+ },
588
+ "messaging.status_callback": {
589
+ AccountSid: randomSid("AC"),
590
+ ApiVersion: "2010-04-01",
591
+ MessageSid: randomSid("SM"),
592
+ SmsSid: randomSid("SM"),
593
+ MessageStatus: "delivered",
594
+ SmsStatus: "delivered",
595
+ To: "+14155559876",
596
+ From: "+14155550123",
597
+ ErrorCode: ""
598
+ },
599
+ "voice.incoming_call": {
600
+ AccountSid: randomSid("AC"),
601
+ ApiVersion: "2010-04-01",
602
+ CallSid: randomSid("CA"),
603
+ CallStatus: "ringing",
604
+ Direction: "inbound",
605
+ From: "+14155550123",
606
+ To: "+14155559876",
607
+ CallerCity: "SAN FRANCISCO",
608
+ CallerState: "CA",
609
+ CallerCountry: "US",
610
+ CallerZip: "94105",
611
+ CalledCity: "",
612
+ CalledState: "",
613
+ CalledCountry: "US",
614
+ CalledZip: ""
615
+ }
616
+ };
617
+ let twilioParams;
618
+ if (bodyOverride !== void 0) {
619
+ if (typeof bodyOverride === "string") {
620
+ const entries = Array.from(new URLSearchParams(bodyOverride).entries());
621
+ return {
622
+ body: bodyOverride,
623
+ contentType: "application/x-www-form-urlencoded",
624
+ headers: {
625
+ "user-agent": "TwilioProxy/1.1"
626
+ },
627
+ twilioParams: entries
628
+ };
629
+ }
630
+ const overrideParams = asStringRecord(bodyOverride);
631
+ if (!overrideParams) {
632
+ throw new Error("Twilio template body override must be a string or an object");
633
+ }
634
+ twilioParams = overrideParams;
635
+ } else {
636
+ twilioParams = defaultTwilioParamsByTemplate[template];
637
+ }
638
+ return {
639
+ body: formEncode(twilioParams),
640
+ contentType: "application/x-www-form-urlencoded",
641
+ headers: {
642
+ "user-agent": "TwilioProxy/1.1"
643
+ },
644
+ twilioParams: Object.entries(twilioParams)
645
+ };
646
+ }
647
+ async function hmacSign(algorithm, secret, payload) {
648
+ if (!globalThis.crypto?.subtle) {
649
+ throw new Error("crypto.subtle is required for template signature generation");
650
+ }
651
+ const key = await globalThis.crypto.subtle.importKey(
652
+ "raw",
653
+ new TextEncoder().encode(secret),
654
+ { name: "HMAC", hash: algorithm },
655
+ false,
656
+ ["sign"]
657
+ );
658
+ const signature = await globalThis.crypto.subtle.sign(
659
+ "HMAC",
660
+ key,
661
+ new TextEncoder().encode(payload)
662
+ );
663
+ return new Uint8Array(signature);
664
+ }
665
+ function toHex(bytes) {
666
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
667
+ }
668
+ function toBase64(bytes) {
669
+ if (typeof btoa !== "function") {
670
+ return Buffer.from(bytes).toString("base64");
671
+ }
672
+ let binary = "";
673
+ for (const byte of bytes) binary += String.fromCharCode(byte);
674
+ return btoa(binary);
675
+ }
676
+ function buildTwilioSignaturePayload(endpointUrl, params) {
677
+ const sortedParams = params.map(([key, value], index) => ({ key, value, index })).sort(
678
+ (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : a.value < b.value ? -1 : a.value > b.value ? 1 : 0
679
+ );
680
+ let payload = endpointUrl;
681
+ for (const { key, value } of sortedParams) {
682
+ payload += `${key}${value}`;
683
+ }
684
+ return payload;
685
+ }
686
+ async function buildTemplateSendOptions(endpointUrl, options) {
687
+ const method = (options.method ?? "POST").toUpperCase();
688
+ const template = ensureTemplate(options.provider, options.template);
689
+ const event = options.event ?? defaultEvent(options.provider, template);
690
+ const now = /* @__PURE__ */ new Date();
691
+ const built = buildTemplatePayload(options.provider, template, event, now, options.body);
692
+ const headers = {
693
+ "content-type": built.contentType,
694
+ "x-webhooks-cc-template-provider": options.provider,
695
+ "x-webhooks-cc-template-template": template,
696
+ "x-webhooks-cc-template-event": event,
697
+ ...built.headers
698
+ };
699
+ if (options.provider === "stripe") {
700
+ const timestamp = options.timestamp ?? Math.floor(Date.now() / 1e3);
701
+ const signature = await hmacSign("SHA-256", options.secret, `${timestamp}.${built.body}`);
702
+ headers["stripe-signature"] = `t=${timestamp},v1=${toHex(signature)}`;
703
+ }
704
+ if (options.provider === "github") {
705
+ headers["x-github-event"] = event;
706
+ headers["x-github-delivery"] = randomUuid();
707
+ const signature = await hmacSign("SHA-256", options.secret, built.body);
708
+ headers["x-hub-signature-256"] = `sha256=${toHex(signature)}`;
709
+ }
710
+ if (options.provider === "shopify") {
711
+ headers["x-shopify-topic"] = event;
712
+ const signature = await hmacSign("SHA-256", options.secret, built.body);
713
+ headers["x-shopify-hmac-sha256"] = toBase64(signature);
714
+ }
715
+ if (options.provider === "twilio") {
716
+ const signaturePayload = built.twilioParams ? buildTwilioSignaturePayload(endpointUrl, built.twilioParams) : `${endpointUrl}${built.body}`;
717
+ const signature = await hmacSign("SHA-1", options.secret, signaturePayload);
718
+ headers["x-twilio-signature"] = toBase64(signature);
719
+ }
720
+ return {
721
+ method,
722
+ headers: {
723
+ ...headers,
724
+ ...options.headers ?? {}
725
+ },
726
+ body: built.body
727
+ };
728
+ }
729
+
148
730
  // src/client.ts
149
731
  var DEFAULT_BASE_URL = "https://webhooks.cc";
150
732
  var DEFAULT_WEBHOOK_URL = "https://go.webhooks.cc";
@@ -164,6 +746,19 @@ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
164
746
  "upgrade"
165
747
  ]);
166
748
  var SENSITIVE_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "proxy-authorization", "set-cookie"]);
749
+ var PROXY_HEADERS = /* @__PURE__ */ new Set([
750
+ "cdn-loop",
751
+ "cf-connecting-ip",
752
+ "cf-ipcountry",
753
+ "cf-ray",
754
+ "cf-visitor",
755
+ "via",
756
+ "x-forwarded-for",
757
+ "x-forwarded-host",
758
+ "x-forwarded-proto",
759
+ "x-real-ip",
760
+ "true-client-ip"
761
+ ]);
167
762
  var ApiError = WebhooksCCError;
168
763
  function mapStatusToError(status, message, response) {
169
764
  const isGeneric = message.length < 30;
@@ -248,6 +843,15 @@ var WebhooksCC = class {
248
843
  body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : void 0,
249
844
  signal: AbortSignal.timeout(this.timeout)
250
845
  });
846
+ },
847
+ sendTemplate: async (slug, options) => {
848
+ validatePathSegment(slug, "slug");
849
+ if (!options.secret || typeof options.secret !== "string") {
850
+ throw new Error("sendTemplate requires a non-empty secret");
851
+ }
852
+ const endpointUrl = `${this.webhookUrl}/w/${slug}`;
853
+ const sendOptions = await buildTemplateSendOptions(endpointUrl, options);
854
+ return this.endpoints.send(slug, sendOptions);
251
855
  }
252
856
  };
253
857
  this.requests = {
@@ -342,7 +946,7 @@ var WebhooksCC = class {
342
946
  const headers = {};
343
947
  for (const [key, val] of Object.entries(captured.headers)) {
344
948
  const lower = key.toLowerCase();
345
- if (!HOP_BY_HOP_HEADERS.has(lower) && !SENSITIVE_HEADERS.has(lower)) {
949
+ if (!HOP_BY_HOP_HEADERS.has(lower) && !SENSITIVE_HEADERS.has(lower) && !PROXY_HEADERS.has(lower)) {
346
950
  headers[key] = val;
347
951
  }
348
952
  }
@@ -419,7 +1023,8 @@ var WebhooksCC = class {
419
1023
  controller.signal.addEventListener(
420
1024
  "abort",
421
1025
  () => {
422
- response.body?.cancel();
1026
+ response.body?.cancel().catch(() => {
1027
+ });
423
1028
  },
424
1029
  { once: true }
425
1030
  );
@@ -592,6 +1197,16 @@ var WebhooksCC = class {
592
1197
  send: {
593
1198
  description: "Send a test webhook to endpoint",
594
1199
  params: { slug: "string", method: "string?", headers: "object?", body: "unknown?" }
1200
+ },
1201
+ sendTemplate: {
1202
+ description: "Send a provider template webhook with signed headers",
1203
+ params: {
1204
+ slug: "string",
1205
+ provider: '"stripe"|"github"|"shopify"|"twilio"',
1206
+ template: "string?",
1207
+ secret: "string",
1208
+ event: "string?"
1209
+ }
595
1210
  }
596
1211
  },
597
1212
  requests: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webhooks-cc/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",