@withaevum/sdk 1.3.9 → 1.3.11

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.ts CHANGED
@@ -8,7 +8,7 @@ export { CalendarAPI } from './calendar';
8
8
  export { AnalyticsAPI } from './analytics';
9
9
  export { AvailabilityAPI } from './availability';
10
10
  export { PaymentsAPI } from './payments';
11
- export { verifyWebhook, WebhookVerificationError } from './webhooks';
12
- export type { WebhookEvent } from './webhooks';
11
+ export { verifyWebhook, WebhookVerificationError, extractWebhookSignature, isBookingEvent, isPaymentEvent, isBookingCreatedEvent, isBookingUpdatedEvent, isBookingCancelledEvent, isBookingConfirmedEvent, isPaymentSucceededEvent, isPaymentFailedEvent, isPaymentRefundedEvent, isWebhookTestEvent, } from './webhooks';
12
+ export type { WebhookEvent, WebhookEventType, BookingCreatedEvent, BookingUpdatedEvent, BookingCancelledEvent, BookingConfirmedEvent, PaymentSucceededEvent, PaymentFailedEvent, PaymentRefundedEvent, WebhookTestEvent, } from './webhooks';
13
13
  export * from './types';
14
14
  export * from './errors';
package/dist/index.js CHANGED
@@ -9,6 +9,6 @@ export { CalendarAPI } from './calendar';
9
9
  export { AnalyticsAPI } from './analytics';
10
10
  export { AvailabilityAPI } from './availability';
11
11
  export { PaymentsAPI } from './payments';
12
- export { verifyWebhook, WebhookVerificationError } from './webhooks';
12
+ export { verifyWebhook, WebhookVerificationError, extractWebhookSignature, isBookingEvent, isPaymentEvent, isBookingCreatedEvent, isBookingUpdatedEvent, isBookingCancelledEvent, isBookingConfirmedEvent, isPaymentSucceededEvent, isPaymentFailedEvent, isPaymentRefundedEvent, isWebhookTestEvent, } from './webhooks';
13
13
  export * from './types';
14
14
  export * from './errors';
@@ -1,20 +1,176 @@
1
+ export type WebhookEventType = 'booking.created' | 'booking.updated' | 'booking.cancelled' | 'booking.confirmed' | 'payment.succeeded' | 'payment.failed' | 'payment.refunded' | 'offering.created' | 'offering.updated' | 'offering.deleted' | 'availability.updated' | 'webhook.test';
1
2
  export interface WebhookEvent {
2
- type: string;
3
+ type: WebhookEventType;
3
4
  version: string;
4
5
  id: string;
5
6
  delivery_id: string;
6
7
  timestamp: string;
7
8
  data: Record<string, unknown>;
8
9
  }
10
+ export interface BookingCreatedEvent extends WebhookEvent {
11
+ type: 'booking.created';
12
+ data: {
13
+ id: string;
14
+ organization_id: string;
15
+ customer_id: string | null;
16
+ status: string;
17
+ start_time: string;
18
+ end_time: string;
19
+ price_cents: number | null;
20
+ notes: string | null;
21
+ provider_ids: string[];
22
+ offering_ids: string[];
23
+ customer?: {
24
+ id: string;
25
+ name: string | null;
26
+ email: string | null;
27
+ phone: string | null;
28
+ };
29
+ };
30
+ }
31
+ export interface BookingUpdatedEvent extends WebhookEvent {
32
+ type: 'booking.updated';
33
+ data: {
34
+ id: string;
35
+ organization_id: string;
36
+ customer_id: string | null;
37
+ status: string;
38
+ start_time: string;
39
+ end_time: string;
40
+ price_cents: number | null;
41
+ notes: string | null;
42
+ provider_ids: string[];
43
+ offering_ids: string[];
44
+ previous_status?: string;
45
+ customer?: {
46
+ id: string;
47
+ name: string | null;
48
+ email: string | null;
49
+ phone: string | null;
50
+ };
51
+ };
52
+ }
53
+ export interface BookingCancelledEvent extends WebhookEvent {
54
+ type: 'booking.cancelled';
55
+ data: {
56
+ id: string;
57
+ organization_id: string;
58
+ cancelled_at: string;
59
+ cancellation_reason?: string;
60
+ [key: string]: unknown;
61
+ };
62
+ }
63
+ export interface BookingConfirmedEvent extends WebhookEvent {
64
+ type: 'booking.confirmed';
65
+ data: {
66
+ id: string;
67
+ organization_id: string;
68
+ confirmed_at: string;
69
+ [key: string]: unknown;
70
+ };
71
+ }
72
+ export interface PaymentSucceededEvent extends WebhookEvent {
73
+ type: 'payment.succeeded';
74
+ data: {
75
+ id: string;
76
+ booking_id: string;
77
+ organization_id: string;
78
+ amount_cents: number;
79
+ currency: string;
80
+ payment_intent_id: string;
81
+ charged_at: string;
82
+ customer_id: string | null;
83
+ };
84
+ }
85
+ export interface PaymentFailedEvent extends WebhookEvent {
86
+ type: 'payment.failed';
87
+ data: {
88
+ id: string;
89
+ booking_id: string;
90
+ organization_id: string;
91
+ amount_cents: number;
92
+ payment_intent_id: string;
93
+ failure_reason?: string;
94
+ customer_id: string | null;
95
+ };
96
+ }
97
+ export interface PaymentRefundedEvent extends WebhookEvent {
98
+ type: 'payment.refunded';
99
+ data: {
100
+ id: string;
101
+ booking_id: string;
102
+ organization_id: string;
103
+ refund_amount_cents: number;
104
+ refunded_at: string;
105
+ customer_id: string | null;
106
+ };
107
+ }
108
+ export interface WebhookTestEvent extends WebhookEvent {
109
+ type: 'webhook.test';
110
+ data: {
111
+ message: string;
112
+ test: boolean;
113
+ organization_id: string;
114
+ };
115
+ }
9
116
  export declare class WebhookVerificationError extends Error {
10
117
  constructor(message: string);
11
118
  }
119
+ /**
120
+ * Extract webhook signature from request headers
121
+ * @param headers - Request headers (Headers object or Record<string, string>)
122
+ * @returns Signature string or null if not found
123
+ */
124
+ export declare function extractWebhookSignature(headers: Headers | Record<string, string | string[] | null | undefined>): string | null;
12
125
  /**
13
126
  * Verify webhook signature and parse event
127
+ * Supports secret rotation by checking previous secret if provided and not expired
14
128
  * @param body - Raw request body as string
15
129
  * @param signature - Signature from X-Aevum-Signature header
16
130
  * @param secret - Webhook secret
131
+ * @param previousSecret - Previous secret (optional, for secret rotation grace period)
132
+ * @param previousSecretExpiresAt - When previous secret expires (optional, ISO 8601 string or Date)
17
133
  * @returns Parsed webhook event
18
134
  * @throws WebhookVerificationError if signature verification fails
19
135
  */
20
- export declare function verifyWebhook(body: string, signature: string, secret: string): WebhookEvent;
136
+ export declare function verifyWebhook(body: string, signature: string, secret: string, previousSecret?: string | null, previousSecretExpiresAt?: string | Date | null): WebhookEvent;
137
+ /**
138
+ * Type guard: Check if event is a booking event
139
+ */
140
+ export declare function isBookingEvent(event: WebhookEvent): event is BookingCreatedEvent | BookingUpdatedEvent | BookingCancelledEvent | BookingConfirmedEvent;
141
+ /**
142
+ * Type guard: Check if event is a payment event
143
+ */
144
+ export declare function isPaymentEvent(event: WebhookEvent): event is PaymentSucceededEvent | PaymentFailedEvent | PaymentRefundedEvent;
145
+ /**
146
+ * Type guard: Check if event is booking.created
147
+ */
148
+ export declare function isBookingCreatedEvent(event: WebhookEvent): event is BookingCreatedEvent;
149
+ /**
150
+ * Type guard: Check if event is booking.updated
151
+ */
152
+ export declare function isBookingUpdatedEvent(event: WebhookEvent): event is BookingUpdatedEvent;
153
+ /**
154
+ * Type guard: Check if event is booking.cancelled
155
+ */
156
+ export declare function isBookingCancelledEvent(event: WebhookEvent): event is BookingCancelledEvent;
157
+ /**
158
+ * Type guard: Check if event is booking.confirmed
159
+ */
160
+ export declare function isBookingConfirmedEvent(event: WebhookEvent): event is BookingConfirmedEvent;
161
+ /**
162
+ * Type guard: Check if event is payment.succeeded
163
+ */
164
+ export declare function isPaymentSucceededEvent(event: WebhookEvent): event is PaymentSucceededEvent;
165
+ /**
166
+ * Type guard: Check if event is payment.failed
167
+ */
168
+ export declare function isPaymentFailedEvent(event: WebhookEvent): event is PaymentFailedEvent;
169
+ /**
170
+ * Type guard: Check if event is payment.refunded
171
+ */
172
+ export declare function isPaymentRefundedEvent(event: WebhookEvent): event is PaymentRefundedEvent;
173
+ /**
174
+ * Type guard: Check if event is webhook.test
175
+ */
176
+ export declare function isWebhookTestEvent(event: WebhookEvent): event is WebhookTestEvent;
package/dist/webhooks.js CHANGED
@@ -7,15 +7,36 @@ export class WebhookVerificationError extends Error {
7
7
  this.name = 'WebhookVerificationError';
8
8
  }
9
9
  }
10
+ /**
11
+ * Extract webhook signature from request headers
12
+ * @param headers - Request headers (Headers object or Record<string, string>)
13
+ * @returns Signature string or null if not found
14
+ */
15
+ export function extractWebhookSignature(headers) {
16
+ if (headers instanceof Headers) {
17
+ return headers.get('x-aevum-signature');
18
+ }
19
+ const signature = headers['x-aevum-signature'];
20
+ if (typeof signature === 'string') {
21
+ return signature;
22
+ }
23
+ if (Array.isArray(signature) && signature.length > 0) {
24
+ return signature[0];
25
+ }
26
+ return null;
27
+ }
10
28
  /**
11
29
  * Verify webhook signature and parse event
30
+ * Supports secret rotation by checking previous secret if provided and not expired
12
31
  * @param body - Raw request body as string
13
32
  * @param signature - Signature from X-Aevum-Signature header
14
33
  * @param secret - Webhook secret
34
+ * @param previousSecret - Previous secret (optional, for secret rotation grace period)
35
+ * @param previousSecretExpiresAt - When previous secret expires (optional, ISO 8601 string or Date)
15
36
  * @returns Parsed webhook event
16
37
  * @throws WebhookVerificationError if signature verification fails
17
38
  */
18
- export function verifyWebhook(body, signature, secret) {
39
+ export function verifyWebhook(body, signature, secret, previousSecret, previousSecretExpiresAt) {
19
40
  if (!body || !signature || !secret) {
20
41
  throw new WebhookVerificationError('Missing required parameters: body, signature, or secret');
21
42
  }
@@ -25,13 +46,30 @@ export function verifyWebhook(body, signature, secret) {
25
46
  throw new WebhookVerificationError('Invalid signature format. Expected format: sha256=<hex>');
26
47
  }
27
48
  const providedSignature = signatureMatch[1];
28
- // Compute signature with secret
49
+ // Compute signature with current secret
29
50
  const computedSignature = crypto
30
51
  .createHmac('sha256', secret)
31
52
  .update(body)
32
53
  .digest('hex');
33
54
  // Compare signatures using constant-time comparison
34
- if (!crypto.timingSafeEqual(Buffer.from(providedSignature, 'hex'), Buffer.from(computedSignature, 'hex'))) {
55
+ let isValid = crypto.timingSafeEqual(Buffer.from(providedSignature, 'hex'), Buffer.from(computedSignature, 'hex'));
56
+ // If current secret doesn't match and previous secret is provided, check it
57
+ if (!isValid &&
58
+ previousSecret &&
59
+ previousSecretExpiresAt) {
60
+ const expiresAt = typeof previousSecretExpiresAt === 'string'
61
+ ? new Date(previousSecretExpiresAt)
62
+ : previousSecretExpiresAt;
63
+ // Check if previous secret is still valid (within grace period)
64
+ if (new Date() < expiresAt) {
65
+ const previousComputedSignature = crypto
66
+ .createHmac('sha256', previousSecret)
67
+ .update(body)
68
+ .digest('hex');
69
+ isValid = crypto.timingSafeEqual(Buffer.from(providedSignature, 'hex'), Buffer.from(previousComputedSignature, 'hex'));
70
+ }
71
+ }
72
+ if (!isValid) {
35
73
  throw new WebhookVerificationError('Invalid webhook signature');
36
74
  }
37
75
  // Parse and validate event
@@ -48,3 +86,68 @@ export function verifyWebhook(body, signature, secret) {
48
86
  }
49
87
  return event;
50
88
  }
89
+ /**
90
+ * Type guard: Check if event is a booking event
91
+ */
92
+ export function isBookingEvent(event) {
93
+ return (event.type === 'booking.created' ||
94
+ event.type === 'booking.updated' ||
95
+ event.type === 'booking.cancelled' ||
96
+ event.type === 'booking.confirmed');
97
+ }
98
+ /**
99
+ * Type guard: Check if event is a payment event
100
+ */
101
+ export function isPaymentEvent(event) {
102
+ return (event.type === 'payment.succeeded' ||
103
+ event.type === 'payment.failed' ||
104
+ event.type === 'payment.refunded');
105
+ }
106
+ /**
107
+ * Type guard: Check if event is booking.created
108
+ */
109
+ export function isBookingCreatedEvent(event) {
110
+ return event.type === 'booking.created';
111
+ }
112
+ /**
113
+ * Type guard: Check if event is booking.updated
114
+ */
115
+ export function isBookingUpdatedEvent(event) {
116
+ return event.type === 'booking.updated';
117
+ }
118
+ /**
119
+ * Type guard: Check if event is booking.cancelled
120
+ */
121
+ export function isBookingCancelledEvent(event) {
122
+ return event.type === 'booking.cancelled';
123
+ }
124
+ /**
125
+ * Type guard: Check if event is booking.confirmed
126
+ */
127
+ export function isBookingConfirmedEvent(event) {
128
+ return event.type === 'booking.confirmed';
129
+ }
130
+ /**
131
+ * Type guard: Check if event is payment.succeeded
132
+ */
133
+ export function isPaymentSucceededEvent(event) {
134
+ return event.type === 'payment.succeeded';
135
+ }
136
+ /**
137
+ * Type guard: Check if event is payment.failed
138
+ */
139
+ export function isPaymentFailedEvent(event) {
140
+ return event.type === 'payment.failed';
141
+ }
142
+ /**
143
+ * Type guard: Check if event is payment.refunded
144
+ */
145
+ export function isPaymentRefundedEvent(event) {
146
+ return event.type === 'payment.refunded';
147
+ }
148
+ /**
149
+ * Type guard: Check if event is webhook.test
150
+ */
151
+ export function isWebhookTestEvent(event) {
152
+ return event.type === 'webhook.test';
153
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withaevum/sdk",
3
- "version": "1.3.9",
3
+ "version": "1.3.11",
4
4
  "description": "TypeScript SDK for the Aevum API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",