@garuhq/node 0.12.0 → 0.12.1

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 CHANGED
@@ -3,6 +3,27 @@
3
3
  All notable changes to `@garuhq/node` are documented in this file. Format:
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).
5
5
 
6
+ ## [0.12.1] — 2026-05-19
7
+
8
+ ### Fixed
9
+
10
+ - `webhookEvents.resend(id)` now auto-attaches `X-Idempotency-Key`
11
+ (UUIDv4) so transient transport retries (5xx → SDK backoff) cannot
12
+ create duplicate clones. Previously, a 503 mid-flight after the
13
+ backend had already committed the clone could trigger an SDK retry
14
+ and produce a second clone with a different id. With the
15
+ idempotency key in place, the backend returns the original clone on
16
+ the second call within 24h. Pass `{ idempotencyKey }` to dedupe
17
+ across your own retry layer.
18
+
19
+ ### Added
20
+
21
+ - `ResendWebhookEventParams` type exported from the package root —
22
+ the optional `{ idempotencyKey?: string }` for `resend()`.
23
+ - README quickstart entry for `webhookEvents`
24
+ (`list` / `get` / `resend` / `retry`), including the audit-trail
25
+ contract and the SDK→gateway idempotency note.
26
+
6
27
  ## [0.12.0] — 2026-05-19
7
28
 
8
29
  ### Added
package/README.md CHANGED
@@ -292,6 +292,38 @@ app.post('/webhooks/garu', express.raw({ type: 'application/json' }), (req, res)
292
292
  > [!IMPORTANT]
293
293
  > Always pass the raw request body to `verify()`. Parsing and re-serializing JSON will break the signature check.
294
294
 
295
+ ## Webhook events
296
+
297
+ The seller-facing delivery log for outbound webhooks. Use it to audit deliveries, surface failures, and replay events when a customer's endpoint missed one. Webhook endpoint *configuration* (URL, subscribed events, secret) is still dashboard-only — this resource only covers the event log + manual retries.
298
+
299
+ ```ts
300
+ // Surface anything that didn't make it through
301
+ const failed = await garu.webhookEvents.list({ status: 'failed', limit: 50 });
302
+
303
+ // Inspect one event end-to-end
304
+ const event = await garu.webhookEvents.get(42);
305
+ console.log(event.responseStatus, event.responseBody);
306
+
307
+ // Audit-trail-preserving replay (recommended)
308
+ const clone = await garu.webhookEvents.resend(42);
309
+ clone.id !== event.id; // true — fresh row with its own id
310
+ clone.manualResendOf === event.id; // true — points back at the source
311
+ ```
312
+
313
+ `resend(id)` is the audit-preserving counterpart to `retry(id)` — the backend inserts a fresh event whose `manualResendOf` points back at the source, then dispatches that clone. The original row stays exactly as it was, so the historical record of the prior failure (status, response status/body, attempts) survives. Works on any source status (`success` / `failed` / `pending`).
314
+
315
+ Outbound deliveries of a resent event carry `Idempotency-Key: resend_<originalId>`, so recipient handlers can distinguish a resend from a fresh delivery both by the header prefix and by reading the response payload's `manualResendOf` field.
316
+
317
+ > [!NOTE]
318
+ > The SDK auto-attaches `X-Idempotency-Key` (UUIDv4) on `resend()` so transient transport retries can't create duplicate clones. Pass `{ idempotencyKey }` to dedupe across your own retry layer.
319
+
320
+ | Method | Purpose |
321
+ | ------------------------------- | ---------------------------------------------------------------------------------- |
322
+ | `list(params?)` | Paginated event log. Filter by `status`, `eventType`, `endpointId`. Newest first. |
323
+ | `get(id)` | One event — full payload, endpoint snapshot, most recent response. |
324
+ | `resend(id, params?)` | Clone-on-resend. Returns the new event; original is untouched. **Preferred.** |
325
+ | `retry(id)` | Legacy in-place reset (mutates the original row). Soft-deprecated. |
326
+
295
327
  ## Error handling
296
328
 
297
329
  Every error extends `GaruError`. API errors include `status`, `requestId`, and `body`.
package/dist/index.cjs CHANGED
@@ -943,6 +943,11 @@ var WebhookEvents = class {
943
943
  * original — distinguishable both by the `resend_` prefix and by reading
944
944
  * the response payload's `manualResendOf` field.
945
945
  *
946
+ * **SDK→gateway dedup**: the SDK auto-attaches `X-Idempotency-Key`
947
+ * (UUIDv4 unless you pass `idempotencyKey`) so transient transport
948
+ * retries (5xx → SDK backoff) cannot create duplicate clones — the
949
+ * backend returns the original clone on the second call within 24h.
950
+ *
946
951
  * Returns the *clone* event (new id), not the original. The original is
947
952
  * unchanged on the server.
948
953
  *
@@ -952,10 +957,12 @@ var WebhookEvents = class {
952
957
  * clone.id !== event.id; // true — clone has its own id
953
958
  * clone.manualResendOf === event.id; // true — points back at the source
954
959
  */
955
- async resend(id) {
960
+ async resend(id, params = {}) {
961
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
956
962
  return this.http.call(
957
963
  (signal) => this.http.client.POST(`/api/webhook-events/${id}/resend`, {
958
964
  body: {},
965
+ headers: { "X-Idempotency-Key": idempotencyKey },
959
966
  signal
960
967
  }).then((r) => r)
961
968
  );
package/dist/index.d.cts CHANGED
@@ -618,6 +618,16 @@ interface ListWebhookEventsParams {
618
618
  /** Filter by the destination endpoint that should receive (or received) the event. */
619
619
  endpointId?: number;
620
620
  }
621
+ interface ResendWebhookEventParams {
622
+ /**
623
+ * SDK→gateway idempotency key. If omitted, the SDK generates a UUIDv4
624
+ * and forwards it as `X-Idempotency-Key`. Within 24h the backend
625
+ * returns the original clone instead of creating a new one — pass a
626
+ * stable key from your own retry layer to dedupe across SDK
627
+ * invocations.
628
+ */
629
+ idempotencyKey?: string;
630
+ }
621
631
  /**
622
632
  * Per-product portal customization (Atletia coach-as-product modeling and
623
633
  * any other B2B2C platform). `null` fields inherit from the seller-level
@@ -1163,6 +1173,11 @@ declare class WebhookEvents {
1163
1173
  * original — distinguishable both by the `resend_` prefix and by reading
1164
1174
  * the response payload's `manualResendOf` field.
1165
1175
  *
1176
+ * **SDK→gateway dedup**: the SDK auto-attaches `X-Idempotency-Key`
1177
+ * (UUIDv4 unless you pass `idempotencyKey`) so transient transport
1178
+ * retries (5xx → SDK backoff) cannot create duplicate clones — the
1179
+ * backend returns the original clone on the second call within 24h.
1180
+ *
1166
1181
  * Returns the *clone* event (new id), not the original. The original is
1167
1182
  * unchanged on the server.
1168
1183
  *
@@ -1172,7 +1187,7 @@ declare class WebhookEvents {
1172
1187
  * clone.id !== event.id; // true — clone has its own id
1173
1188
  * clone.manualResendOf === event.id; // true — points back at the source
1174
1189
  */
1175
- resend(id: number): Promise<WebhookEvent>;
1190
+ resend(id: number, params?: ResendWebhookEventParams): Promise<WebhookEvent>;
1176
1191
  }
1177
1192
 
1178
1193
  interface GaruOptions {
@@ -1274,4 +1289,4 @@ declare class GaruServerError extends GaruAPIError {
1274
1289
  constructor(message: string, status: number, requestId: string | null, body: unknown);
1275
1290
  }
1276
1291
 
1277
- export { type CancelAtPeriodEndScheduledChargeParams, type CancelRecurrenceScheduledChargeParams, type CardInfo, type ChangePaymentMethodScheduledChargeParams, type Charge, type ChargeList, type ChargeStatus, type CreateChargeParams, type CreateCustomerParams, type CreateScheduledChargeParams, type Customer, type CustomerList, type CustomerRecord, type FailurePayload, Garu, GaruAPIError, GaruAuthenticationError, GaruConnectionError, GaruError, type GaruErrorCode, type GaruFailureCode, GaruNotFoundError, type GaruOptions, GaruPermissionError, GaruRateLimitError, GaruServerError, GaruSignatureVerificationError, GaruValidationError, type ListChargesParams, type ListCustomersParams, type ListProductsParams, type ListScheduledChargeAttemptsParams, type ListScheduledChargesParams, type ListWebhookEventsParams, type MarkPaidScheduledChargeParams, type MetaFeatures, type MetaResponse, type PaginatedList, type PauseScheduledChargeParams, type PaymentMethod, type PaymentMethodExpiredPayload, type PaymentMethodExpiringPayload, type PostponeScheduledChargeParams, type Product, type ProductList, type ProductPortalConfig, type RecurrenceConfig, type RecurrenceInterval, type RefundChargeParams, type ScheduledChargeActor, type ScheduledChargeAttempt, type ScheduledChargeAttemptList, type ScheduledChargeAttemptSource, type ScheduledChargeAttemptStatus, type ScheduledChargeDetail, type ScheduledChargeEvent, type ScheduledChargeEventType, type ScheduledChargeLinkedTransaction, type ScheduledChargeList, type ScheduledChargeRecord, type ScheduledChargeStatus, type ScheduledChargeType, type ScheduledPaymentMethod, type SetProductPortalConfigParams, type UpdateCustomerParams, type VerifiedWebhook, type VerifyWebhookParams, type WebhookEvent, type WebhookEventEndpoint, type WebhookEventList, type WebhookEventStatus, type WirePaymentMethodId, webhooks };
1292
+ export { type CancelAtPeriodEndScheduledChargeParams, type CancelRecurrenceScheduledChargeParams, type CardInfo, type ChangePaymentMethodScheduledChargeParams, type Charge, type ChargeList, type ChargeStatus, type CreateChargeParams, type CreateCustomerParams, type CreateScheduledChargeParams, type Customer, type CustomerList, type CustomerRecord, type FailurePayload, Garu, GaruAPIError, GaruAuthenticationError, GaruConnectionError, GaruError, type GaruErrorCode, type GaruFailureCode, GaruNotFoundError, type GaruOptions, GaruPermissionError, GaruRateLimitError, GaruServerError, GaruSignatureVerificationError, GaruValidationError, type ListChargesParams, type ListCustomersParams, type ListProductsParams, type ListScheduledChargeAttemptsParams, type ListScheduledChargesParams, type ListWebhookEventsParams, type MarkPaidScheduledChargeParams, type MetaFeatures, type MetaResponse, type PaginatedList, type PauseScheduledChargeParams, type PaymentMethod, type PaymentMethodExpiredPayload, type PaymentMethodExpiringPayload, type PostponeScheduledChargeParams, type Product, type ProductList, type ProductPortalConfig, type RecurrenceConfig, type RecurrenceInterval, type RefundChargeParams, type ResendWebhookEventParams, type ScheduledChargeActor, type ScheduledChargeAttempt, type ScheduledChargeAttemptList, type ScheduledChargeAttemptSource, type ScheduledChargeAttemptStatus, type ScheduledChargeDetail, type ScheduledChargeEvent, type ScheduledChargeEventType, type ScheduledChargeLinkedTransaction, type ScheduledChargeList, type ScheduledChargeRecord, type ScheduledChargeStatus, type ScheduledChargeType, type ScheduledPaymentMethod, type SetProductPortalConfigParams, type UpdateCustomerParams, type VerifiedWebhook, type VerifyWebhookParams, type WebhookEvent, type WebhookEventEndpoint, type WebhookEventList, type WebhookEventStatus, type WirePaymentMethodId, webhooks };
package/dist/index.d.ts CHANGED
@@ -618,6 +618,16 @@ interface ListWebhookEventsParams {
618
618
  /** Filter by the destination endpoint that should receive (or received) the event. */
619
619
  endpointId?: number;
620
620
  }
621
+ interface ResendWebhookEventParams {
622
+ /**
623
+ * SDK→gateway idempotency key. If omitted, the SDK generates a UUIDv4
624
+ * and forwards it as `X-Idempotency-Key`. Within 24h the backend
625
+ * returns the original clone instead of creating a new one — pass a
626
+ * stable key from your own retry layer to dedupe across SDK
627
+ * invocations.
628
+ */
629
+ idempotencyKey?: string;
630
+ }
621
631
  /**
622
632
  * Per-product portal customization (Atletia coach-as-product modeling and
623
633
  * any other B2B2C platform). `null` fields inherit from the seller-level
@@ -1163,6 +1173,11 @@ declare class WebhookEvents {
1163
1173
  * original — distinguishable both by the `resend_` prefix and by reading
1164
1174
  * the response payload's `manualResendOf` field.
1165
1175
  *
1176
+ * **SDK→gateway dedup**: the SDK auto-attaches `X-Idempotency-Key`
1177
+ * (UUIDv4 unless you pass `idempotencyKey`) so transient transport
1178
+ * retries (5xx → SDK backoff) cannot create duplicate clones — the
1179
+ * backend returns the original clone on the second call within 24h.
1180
+ *
1166
1181
  * Returns the *clone* event (new id), not the original. The original is
1167
1182
  * unchanged on the server.
1168
1183
  *
@@ -1172,7 +1187,7 @@ declare class WebhookEvents {
1172
1187
  * clone.id !== event.id; // true — clone has its own id
1173
1188
  * clone.manualResendOf === event.id; // true — points back at the source
1174
1189
  */
1175
- resend(id: number): Promise<WebhookEvent>;
1190
+ resend(id: number, params?: ResendWebhookEventParams): Promise<WebhookEvent>;
1176
1191
  }
1177
1192
 
1178
1193
  interface GaruOptions {
@@ -1274,4 +1289,4 @@ declare class GaruServerError extends GaruAPIError {
1274
1289
  constructor(message: string, status: number, requestId: string | null, body: unknown);
1275
1290
  }
1276
1291
 
1277
- export { type CancelAtPeriodEndScheduledChargeParams, type CancelRecurrenceScheduledChargeParams, type CardInfo, type ChangePaymentMethodScheduledChargeParams, type Charge, type ChargeList, type ChargeStatus, type CreateChargeParams, type CreateCustomerParams, type CreateScheduledChargeParams, type Customer, type CustomerList, type CustomerRecord, type FailurePayload, Garu, GaruAPIError, GaruAuthenticationError, GaruConnectionError, GaruError, type GaruErrorCode, type GaruFailureCode, GaruNotFoundError, type GaruOptions, GaruPermissionError, GaruRateLimitError, GaruServerError, GaruSignatureVerificationError, GaruValidationError, type ListChargesParams, type ListCustomersParams, type ListProductsParams, type ListScheduledChargeAttemptsParams, type ListScheduledChargesParams, type ListWebhookEventsParams, type MarkPaidScheduledChargeParams, type MetaFeatures, type MetaResponse, type PaginatedList, type PauseScheduledChargeParams, type PaymentMethod, type PaymentMethodExpiredPayload, type PaymentMethodExpiringPayload, type PostponeScheduledChargeParams, type Product, type ProductList, type ProductPortalConfig, type RecurrenceConfig, type RecurrenceInterval, type RefundChargeParams, type ScheduledChargeActor, type ScheduledChargeAttempt, type ScheduledChargeAttemptList, type ScheduledChargeAttemptSource, type ScheduledChargeAttemptStatus, type ScheduledChargeDetail, type ScheduledChargeEvent, type ScheduledChargeEventType, type ScheduledChargeLinkedTransaction, type ScheduledChargeList, type ScheduledChargeRecord, type ScheduledChargeStatus, type ScheduledChargeType, type ScheduledPaymentMethod, type SetProductPortalConfigParams, type UpdateCustomerParams, type VerifiedWebhook, type VerifyWebhookParams, type WebhookEvent, type WebhookEventEndpoint, type WebhookEventList, type WebhookEventStatus, type WirePaymentMethodId, webhooks };
1292
+ export { type CancelAtPeriodEndScheduledChargeParams, type CancelRecurrenceScheduledChargeParams, type CardInfo, type ChangePaymentMethodScheduledChargeParams, type Charge, type ChargeList, type ChargeStatus, type CreateChargeParams, type CreateCustomerParams, type CreateScheduledChargeParams, type Customer, type CustomerList, type CustomerRecord, type FailurePayload, Garu, GaruAPIError, GaruAuthenticationError, GaruConnectionError, GaruError, type GaruErrorCode, type GaruFailureCode, GaruNotFoundError, type GaruOptions, GaruPermissionError, GaruRateLimitError, GaruServerError, GaruSignatureVerificationError, GaruValidationError, type ListChargesParams, type ListCustomersParams, type ListProductsParams, type ListScheduledChargeAttemptsParams, type ListScheduledChargesParams, type ListWebhookEventsParams, type MarkPaidScheduledChargeParams, type MetaFeatures, type MetaResponse, type PaginatedList, type PauseScheduledChargeParams, type PaymentMethod, type PaymentMethodExpiredPayload, type PaymentMethodExpiringPayload, type PostponeScheduledChargeParams, type Product, type ProductList, type ProductPortalConfig, type RecurrenceConfig, type RecurrenceInterval, type RefundChargeParams, type ResendWebhookEventParams, type ScheduledChargeActor, type ScheduledChargeAttempt, type ScheduledChargeAttemptList, type ScheduledChargeAttemptSource, type ScheduledChargeAttemptStatus, type ScheduledChargeDetail, type ScheduledChargeEvent, type ScheduledChargeEventType, type ScheduledChargeLinkedTransaction, type ScheduledChargeList, type ScheduledChargeRecord, type ScheduledChargeStatus, type ScheduledChargeType, type ScheduledPaymentMethod, type SetProductPortalConfigParams, type UpdateCustomerParams, type VerifiedWebhook, type VerifyWebhookParams, type WebhookEvent, type WebhookEventEndpoint, type WebhookEventList, type WebhookEventStatus, type WirePaymentMethodId, webhooks };
package/dist/index.js CHANGED
@@ -937,6 +937,11 @@ var WebhookEvents = class {
937
937
  * original — distinguishable both by the `resend_` prefix and by reading
938
938
  * the response payload's `manualResendOf` field.
939
939
  *
940
+ * **SDK→gateway dedup**: the SDK auto-attaches `X-Idempotency-Key`
941
+ * (UUIDv4 unless you pass `idempotencyKey`) so transient transport
942
+ * retries (5xx → SDK backoff) cannot create duplicate clones — the
943
+ * backend returns the original clone on the second call within 24h.
944
+ *
940
945
  * Returns the *clone* event (new id), not the original. The original is
941
946
  * unchanged on the server.
942
947
  *
@@ -946,10 +951,12 @@ var WebhookEvents = class {
946
951
  * clone.id !== event.id; // true — clone has its own id
947
952
  * clone.manualResendOf === event.id; // true — points back at the source
948
953
  */
949
- async resend(id) {
954
+ async resend(id, params = {}) {
955
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
950
956
  return this.http.call(
951
957
  (signal) => this.http.client.POST(`/api/webhook-events/${id}/resend`, {
952
958
  body: {},
959
+ headers: { "X-Idempotency-Key": idempotencyKey },
953
960
  signal
954
961
  }).then((r) => r)
955
962
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garuhq/node",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Official Node.js / TypeScript SDK for the Garu payment gateway.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://garu.com.br",