@tritonium/api-client 2.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.
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Webhook verification utilities for Tritonium.
3
+ *
4
+ * This module provides functions for verifying webhook signatures and handling
5
+ * incoming webhook events from Tritonium.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { verifyWebhook, WebhookEvent } from './webhooks';
10
+ *
11
+ * const event = verifyWebhook({
12
+ * payload: requestBody,
13
+ * signature: headers['x-tritonium-signature'],
14
+ * timestamp: headers['x-tritonium-timestamp'],
15
+ * secret: 'whsec_your_secret',
16
+ * });
17
+ * ```
18
+ */
19
+ /**
20
+ * Error thrown when webhook verification fails.
21
+ */
22
+ declare class WebhookVerificationError extends Error {
23
+ constructor(message: string);
24
+ }
25
+ /**
26
+ * Error thrown when webhook timestamp is too old.
27
+ */
28
+ declare class WebhookExpiredError extends WebhookVerificationError {
29
+ constructor(message: string);
30
+ }
31
+ /**
32
+ * Error thrown when webhook signature is invalid.
33
+ */
34
+ declare class WebhookSignatureError extends WebhookVerificationError {
35
+ constructor(message: string);
36
+ }
37
+ /**
38
+ * Parsed webhook event from Tritonium.
39
+ */
40
+ interface WebhookEvent<T = Record<string, unknown>> {
41
+ /** Unique identifier for this event */
42
+ eventId: string;
43
+ /** Type of event (e.g., 'review.received', 'alert.triggered') */
44
+ eventType: string;
45
+ /** When the event occurred */
46
+ timestamp: Date;
47
+ /** Tenant that owns this event */
48
+ tenantId: string;
49
+ /** Optional app UUID related to the event */
50
+ appUuid?: string;
51
+ /** Event-specific payload data */
52
+ data: T;
53
+ /** Original JSON payload */
54
+ rawPayload: Record<string, unknown>;
55
+ }
56
+ /**
57
+ * Tritonium webhook event types.
58
+ */
59
+ declare const EventTypes: {
60
+ readonly CRISIS_DETECTED: "crisis.detected";
61
+ readonly INSIGHT_GENERATED: "insight.generated";
62
+ readonly REVIEW_RECEIVED: "review.received";
63
+ readonly ANALYSIS_COMPLETED: "analysis.completed";
64
+ readonly ALERT_TRIGGERED: "alert.triggered";
65
+ readonly TEST_PING: "test.ping";
66
+ };
67
+ type EventType = (typeof EventTypes)[keyof typeof EventTypes];
68
+ /**
69
+ * Options for webhook verification.
70
+ */
71
+ interface VerifyWebhookOptions {
72
+ /** Raw request body (string or Buffer) */
73
+ payload: string | Buffer;
74
+ /** Value of X-Tritonium-Signature header */
75
+ signature: string;
76
+ /** Value of X-Tritonium-Timestamp header */
77
+ timestamp: string;
78
+ /** Your webhook signing secret */
79
+ secret: string;
80
+ /** Maximum age in seconds (default: 300 = 5 minutes) */
81
+ toleranceSeconds?: number;
82
+ }
83
+ /**
84
+ * Verify a webhook signature without parsing the payload.
85
+ *
86
+ * @param payload - Raw request body
87
+ * @param signature - Value of X-Tritonium-Signature header
88
+ * @param secret - Your webhook signing secret
89
+ * @returns true if signature is valid
90
+ */
91
+ declare function verifySignature(payload: string | Buffer, signature: string, secret: string): boolean;
92
+ /**
93
+ * Verify that a webhook timestamp is within acceptable range.
94
+ *
95
+ * @param timestamp - ISO 8601 timestamp string
96
+ * @param toleranceSeconds - Maximum age in seconds (default: 300)
97
+ * @returns true if timestamp is valid
98
+ */
99
+ declare function verifyTimestamp(timestamp: string, toleranceSeconds?: number): boolean;
100
+ /**
101
+ * Verify a webhook and parse the event.
102
+ *
103
+ * This is the main function for processing incoming webhooks. It:
104
+ * 1. Verifies the timestamp is not too old (replay protection)
105
+ * 2. Verifies the HMAC signature
106
+ * 3. Parses and returns the event
107
+ *
108
+ * @param options - Verification options
109
+ * @returns Parsed and verified webhook event
110
+ * @throws WebhookExpiredError if timestamp is too old
111
+ * @throws WebhookSignatureError if signature is invalid
112
+ * @throws WebhookVerificationError if payload cannot be parsed
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * import express from 'express';
117
+ * import { verifyWebhook, WebhookVerificationError } from '@tritonium/api-client/webhooks';
118
+ *
119
+ * const app = express();
120
+ * const WEBHOOK_SECRET = 'whsec_your_secret';
121
+ *
122
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
123
+ * try {
124
+ * const event = verifyWebhook({
125
+ * payload: req.body,
126
+ * signature: req.headers['x-tritonium-signature'] as string,
127
+ * timestamp: req.headers['x-tritonium-timestamp'] as string,
128
+ * secret: WEBHOOK_SECRET,
129
+ * });
130
+ *
131
+ * switch (event.eventType) {
132
+ * case 'review.received':
133
+ * handleReview(event.data);
134
+ * break;
135
+ * case 'alert.triggered':
136
+ * handleAlert(event.data);
137
+ * break;
138
+ * }
139
+ *
140
+ * res.json({ status: 'ok' });
141
+ * } catch (e) {
142
+ * if (e instanceof WebhookVerificationError) {
143
+ * res.status(401).json({ error: e.message });
144
+ * } else {
145
+ * throw e;
146
+ * }
147
+ * }
148
+ * });
149
+ * ```
150
+ */
151
+ declare function verifyWebhook<T = Record<string, unknown>>(options: VerifyWebhookOptions): WebhookEvent<T>;
152
+ /**
153
+ * Alias for verifyWebhook for compatibility with other webhook libraries.
154
+ */
155
+ declare const constructEvent: typeof verifyWebhook;
156
+ /**
157
+ * Type guard to check if an error is a WebhookVerificationError.
158
+ */
159
+ declare function isWebhookError(error: unknown): error is WebhookVerificationError;
160
+ /**
161
+ * Type definitions for specific event payloads.
162
+ */
163
+ interface ReviewReceivedData {
164
+ review_id: string;
165
+ platform: string;
166
+ rating: number;
167
+ title?: string;
168
+ text: string;
169
+ author?: string;
170
+ review_date: string;
171
+ country?: string;
172
+ version?: string;
173
+ }
174
+ interface AlertTriggeredData {
175
+ alert_id: string;
176
+ rule_id: string;
177
+ rule_name: string;
178
+ severity: 'low' | 'medium' | 'high' | 'critical';
179
+ message: string;
180
+ current_value?: number;
181
+ threshold?: number;
182
+ }
183
+ interface CrisisDetectedData {
184
+ severity: 'low' | 'medium' | 'high' | 'critical';
185
+ type: string;
186
+ message: string;
187
+ affected_period?: string;
188
+ metrics?: Record<string, unknown>;
189
+ }
190
+
191
+ export { type AlertTriggeredData, type CrisisDetectedData, type EventType, EventTypes, type ReviewReceivedData, type VerifyWebhookOptions, type WebhookEvent, WebhookExpiredError, WebhookSignatureError, WebhookVerificationError, constructEvent, isWebhookError, verifySignature, verifyTimestamp, verifyWebhook };
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Webhook verification utilities for Tritonium.
3
+ *
4
+ * This module provides functions for verifying webhook signatures and handling
5
+ * incoming webhook events from Tritonium.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { verifyWebhook, WebhookEvent } from './webhooks';
10
+ *
11
+ * const event = verifyWebhook({
12
+ * payload: requestBody,
13
+ * signature: headers['x-tritonium-signature'],
14
+ * timestamp: headers['x-tritonium-timestamp'],
15
+ * secret: 'whsec_your_secret',
16
+ * });
17
+ * ```
18
+ */
19
+ /**
20
+ * Error thrown when webhook verification fails.
21
+ */
22
+ declare class WebhookVerificationError extends Error {
23
+ constructor(message: string);
24
+ }
25
+ /**
26
+ * Error thrown when webhook timestamp is too old.
27
+ */
28
+ declare class WebhookExpiredError extends WebhookVerificationError {
29
+ constructor(message: string);
30
+ }
31
+ /**
32
+ * Error thrown when webhook signature is invalid.
33
+ */
34
+ declare class WebhookSignatureError extends WebhookVerificationError {
35
+ constructor(message: string);
36
+ }
37
+ /**
38
+ * Parsed webhook event from Tritonium.
39
+ */
40
+ interface WebhookEvent<T = Record<string, unknown>> {
41
+ /** Unique identifier for this event */
42
+ eventId: string;
43
+ /** Type of event (e.g., 'review.received', 'alert.triggered') */
44
+ eventType: string;
45
+ /** When the event occurred */
46
+ timestamp: Date;
47
+ /** Tenant that owns this event */
48
+ tenantId: string;
49
+ /** Optional app UUID related to the event */
50
+ appUuid?: string;
51
+ /** Event-specific payload data */
52
+ data: T;
53
+ /** Original JSON payload */
54
+ rawPayload: Record<string, unknown>;
55
+ }
56
+ /**
57
+ * Tritonium webhook event types.
58
+ */
59
+ declare const EventTypes: {
60
+ readonly CRISIS_DETECTED: "crisis.detected";
61
+ readonly INSIGHT_GENERATED: "insight.generated";
62
+ readonly REVIEW_RECEIVED: "review.received";
63
+ readonly ANALYSIS_COMPLETED: "analysis.completed";
64
+ readonly ALERT_TRIGGERED: "alert.triggered";
65
+ readonly TEST_PING: "test.ping";
66
+ };
67
+ type EventType = (typeof EventTypes)[keyof typeof EventTypes];
68
+ /**
69
+ * Options for webhook verification.
70
+ */
71
+ interface VerifyWebhookOptions {
72
+ /** Raw request body (string or Buffer) */
73
+ payload: string | Buffer;
74
+ /** Value of X-Tritonium-Signature header */
75
+ signature: string;
76
+ /** Value of X-Tritonium-Timestamp header */
77
+ timestamp: string;
78
+ /** Your webhook signing secret */
79
+ secret: string;
80
+ /** Maximum age in seconds (default: 300 = 5 minutes) */
81
+ toleranceSeconds?: number;
82
+ }
83
+ /**
84
+ * Verify a webhook signature without parsing the payload.
85
+ *
86
+ * @param payload - Raw request body
87
+ * @param signature - Value of X-Tritonium-Signature header
88
+ * @param secret - Your webhook signing secret
89
+ * @returns true if signature is valid
90
+ */
91
+ declare function verifySignature(payload: string | Buffer, signature: string, secret: string): boolean;
92
+ /**
93
+ * Verify that a webhook timestamp is within acceptable range.
94
+ *
95
+ * @param timestamp - ISO 8601 timestamp string
96
+ * @param toleranceSeconds - Maximum age in seconds (default: 300)
97
+ * @returns true if timestamp is valid
98
+ */
99
+ declare function verifyTimestamp(timestamp: string, toleranceSeconds?: number): boolean;
100
+ /**
101
+ * Verify a webhook and parse the event.
102
+ *
103
+ * This is the main function for processing incoming webhooks. It:
104
+ * 1. Verifies the timestamp is not too old (replay protection)
105
+ * 2. Verifies the HMAC signature
106
+ * 3. Parses and returns the event
107
+ *
108
+ * @param options - Verification options
109
+ * @returns Parsed and verified webhook event
110
+ * @throws WebhookExpiredError if timestamp is too old
111
+ * @throws WebhookSignatureError if signature is invalid
112
+ * @throws WebhookVerificationError if payload cannot be parsed
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * import express from 'express';
117
+ * import { verifyWebhook, WebhookVerificationError } from '@tritonium/api-client/webhooks';
118
+ *
119
+ * const app = express();
120
+ * const WEBHOOK_SECRET = 'whsec_your_secret';
121
+ *
122
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
123
+ * try {
124
+ * const event = verifyWebhook({
125
+ * payload: req.body,
126
+ * signature: req.headers['x-tritonium-signature'] as string,
127
+ * timestamp: req.headers['x-tritonium-timestamp'] as string,
128
+ * secret: WEBHOOK_SECRET,
129
+ * });
130
+ *
131
+ * switch (event.eventType) {
132
+ * case 'review.received':
133
+ * handleReview(event.data);
134
+ * break;
135
+ * case 'alert.triggered':
136
+ * handleAlert(event.data);
137
+ * break;
138
+ * }
139
+ *
140
+ * res.json({ status: 'ok' });
141
+ * } catch (e) {
142
+ * if (e instanceof WebhookVerificationError) {
143
+ * res.status(401).json({ error: e.message });
144
+ * } else {
145
+ * throw e;
146
+ * }
147
+ * }
148
+ * });
149
+ * ```
150
+ */
151
+ declare function verifyWebhook<T = Record<string, unknown>>(options: VerifyWebhookOptions): WebhookEvent<T>;
152
+ /**
153
+ * Alias for verifyWebhook for compatibility with other webhook libraries.
154
+ */
155
+ declare const constructEvent: typeof verifyWebhook;
156
+ /**
157
+ * Type guard to check if an error is a WebhookVerificationError.
158
+ */
159
+ declare function isWebhookError(error: unknown): error is WebhookVerificationError;
160
+ /**
161
+ * Type definitions for specific event payloads.
162
+ */
163
+ interface ReviewReceivedData {
164
+ review_id: string;
165
+ platform: string;
166
+ rating: number;
167
+ title?: string;
168
+ text: string;
169
+ author?: string;
170
+ review_date: string;
171
+ country?: string;
172
+ version?: string;
173
+ }
174
+ interface AlertTriggeredData {
175
+ alert_id: string;
176
+ rule_id: string;
177
+ rule_name: string;
178
+ severity: 'low' | 'medium' | 'high' | 'critical';
179
+ message: string;
180
+ current_value?: number;
181
+ threshold?: number;
182
+ }
183
+ interface CrisisDetectedData {
184
+ severity: 'low' | 'medium' | 'high' | 'critical';
185
+ type: string;
186
+ message: string;
187
+ affected_period?: string;
188
+ metrics?: Record<string, unknown>;
189
+ }
190
+
191
+ export { type AlertTriggeredData, type CrisisDetectedData, type EventType, EventTypes, type ReviewReceivedData, type VerifyWebhookOptions, type WebhookEvent, WebhookExpiredError, WebhookSignatureError, WebhookVerificationError, constructEvent, isWebhookError, verifySignature, verifyTimestamp, verifyWebhook };
@@ -0,0 +1,125 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ function _interopNamespace(e) {
6
+ if (e && e.__esModule) return e;
7
+ var n = Object.create(null);
8
+ if (e) {
9
+ Object.keys(e).forEach(function (k) {
10
+ if (k !== 'default') {
11
+ var d = Object.getOwnPropertyDescriptor(e, k);
12
+ Object.defineProperty(n, k, d.get ? d : {
13
+ enumerable: true,
14
+ get: function () { return e[k]; }
15
+ });
16
+ }
17
+ });
18
+ }
19
+ n.default = e;
20
+ return Object.freeze(n);
21
+ }
22
+
23
+ var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
24
+
25
+ // webhooks.ts
26
+ var WebhookVerificationError = class extends Error {
27
+ constructor(message) {
28
+ super(message);
29
+ this.name = "WebhookVerificationError";
30
+ }
31
+ };
32
+ var WebhookExpiredError = class extends WebhookVerificationError {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "WebhookExpiredError";
36
+ }
37
+ };
38
+ var WebhookSignatureError = class extends WebhookVerificationError {
39
+ constructor(message) {
40
+ super(message);
41
+ this.name = "WebhookSignatureError";
42
+ }
43
+ };
44
+ var EventTypes = {
45
+ CRISIS_DETECTED: "crisis.detected",
46
+ INSIGHT_GENERATED: "insight.generated",
47
+ REVIEW_RECEIVED: "review.received",
48
+ ANALYSIS_COMPLETED: "analysis.completed",
49
+ ALERT_TRIGGERED: "alert.triggered",
50
+ TEST_PING: "test.ping"
51
+ };
52
+ function verifySignature(payload, signature, secret) {
53
+ const payloadBuffer = typeof payload === "string" ? Buffer.from(payload) : payload;
54
+ const receivedSig = signature.startsWith("sha256=") ? signature.slice(7) : signature;
55
+ const expectedSig = crypto__namespace.createHmac("sha256", secret).update(payloadBuffer).digest("hex");
56
+ try {
57
+ return crypto__namespace.timingSafeEqual(
58
+ Buffer.from(receivedSig),
59
+ Buffer.from(expectedSig)
60
+ );
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ function verifyTimestamp(timestamp, toleranceSeconds = 300) {
66
+ try {
67
+ const webhookTime = new Date(timestamp).getTime();
68
+ const now = Date.now();
69
+ const ageMs = Math.abs(now - webhookTime);
70
+ return ageMs <= toleranceSeconds * 1e3;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+ function verifyWebhook(options) {
76
+ const { payload, signature, timestamp, secret, toleranceSeconds = 300 } = options;
77
+ if (!verifyTimestamp(timestamp, toleranceSeconds)) {
78
+ throw new WebhookExpiredError(
79
+ `Webhook timestamp is too old or invalid: ${timestamp}`
80
+ );
81
+ }
82
+ if (!verifySignature(payload, signature, secret)) {
83
+ throw new WebhookSignatureError("Invalid webhook signature");
84
+ }
85
+ let data;
86
+ try {
87
+ const payloadStr = typeof payload === "string" ? payload : payload.toString("utf-8");
88
+ data = JSON.parse(payloadStr);
89
+ } catch (e) {
90
+ throw new WebhookVerificationError(`Invalid webhook payload: ${e}`);
91
+ }
92
+ let eventTimestamp;
93
+ try {
94
+ eventTimestamp = new Date(
95
+ data.timestamp || timestamp
96
+ );
97
+ } catch {
98
+ eventTimestamp = /* @__PURE__ */ new Date();
99
+ }
100
+ return {
101
+ eventId: data.event_id || "",
102
+ eventType: data.event_type || "",
103
+ timestamp: eventTimestamp,
104
+ tenantId: data.tenant_id || "",
105
+ appUuid: data.app_uuid,
106
+ data: data.data || {},
107
+ rawPayload: data
108
+ };
109
+ }
110
+ var constructEvent = verifyWebhook;
111
+ function isWebhookError(error) {
112
+ return error instanceof WebhookVerificationError;
113
+ }
114
+
115
+ exports.EventTypes = EventTypes;
116
+ exports.WebhookExpiredError = WebhookExpiredError;
117
+ exports.WebhookSignatureError = WebhookSignatureError;
118
+ exports.WebhookVerificationError = WebhookVerificationError;
119
+ exports.constructEvent = constructEvent;
120
+ exports.isWebhookError = isWebhookError;
121
+ exports.verifySignature = verifySignature;
122
+ exports.verifyTimestamp = verifyTimestamp;
123
+ exports.verifyWebhook = verifyWebhook;
124
+ //# sourceMappingURL=webhooks.js.map
125
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../webhooks.ts"],"names":["crypto"],"mappings":"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","file":"webhooks.js","sourcesContent":["/**\n * Webhook verification utilities for Tritonium.\n *\n * This module provides functions for verifying webhook signatures and handling\n * incoming webhook events from Tritonium.\n *\n * @example\n * ```typescript\n * import { verifyWebhook, WebhookEvent } from './webhooks';\n *\n * const event = verifyWebhook({\n * payload: requestBody,\n * signature: headers['x-tritonium-signature'],\n * timestamp: headers['x-tritonium-timestamp'],\n * secret: 'whsec_your_secret',\n * });\n * ```\n */\n\nimport * as crypto from 'crypto';\n\n/**\n * Error thrown when webhook verification fails.\n */\nexport class WebhookVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'WebhookVerificationError';\n }\n}\n\n/**\n * Error thrown when webhook timestamp is too old.\n */\nexport class WebhookExpiredError extends WebhookVerificationError {\n constructor(message: string) {\n super(message);\n this.name = 'WebhookExpiredError';\n }\n}\n\n/**\n * Error thrown when webhook signature is invalid.\n */\nexport class WebhookSignatureError extends WebhookVerificationError {\n constructor(message: string) {\n super(message);\n this.name = 'WebhookSignatureError';\n }\n}\n\n/**\n * Parsed webhook event from Tritonium.\n */\nexport interface WebhookEvent<T = Record<string, unknown>> {\n /** Unique identifier for this event */\n eventId: string;\n /** Type of event (e.g., 'review.received', 'alert.triggered') */\n eventType: string;\n /** When the event occurred */\n timestamp: Date;\n /** Tenant that owns this event */\n tenantId: string;\n /** Optional app UUID related to the event */\n appUuid?: string;\n /** Event-specific payload data */\n data: T;\n /** Original JSON payload */\n rawPayload: Record<string, unknown>;\n}\n\n/**\n * Tritonium webhook event types.\n */\nexport const EventTypes = {\n CRISIS_DETECTED: 'crisis.detected',\n INSIGHT_GENERATED: 'insight.generated',\n REVIEW_RECEIVED: 'review.received',\n ANALYSIS_COMPLETED: 'analysis.completed',\n ALERT_TRIGGERED: 'alert.triggered',\n TEST_PING: 'test.ping',\n} as const;\n\nexport type EventType = (typeof EventTypes)[keyof typeof EventTypes];\n\n/**\n * Options for webhook verification.\n */\nexport interface VerifyWebhookOptions {\n /** Raw request body (string or Buffer) */\n payload: string | Buffer;\n /** Value of X-Tritonium-Signature header */\n signature: string;\n /** Value of X-Tritonium-Timestamp header */\n timestamp: string;\n /** Your webhook signing secret */\n secret: string;\n /** Maximum age in seconds (default: 300 = 5 minutes) */\n toleranceSeconds?: number;\n}\n\n/**\n * Verify a webhook signature without parsing the payload.\n *\n * @param payload - Raw request body\n * @param signature - Value of X-Tritonium-Signature header\n * @param secret - Your webhook signing secret\n * @returns true if signature is valid\n */\nexport function verifySignature(\n payload: string | Buffer,\n signature: string,\n secret: string\n): boolean {\n const payloadBuffer =\n typeof payload === 'string' ? Buffer.from(payload) : payload;\n\n // Extract the hex signature (remove 'sha256=' prefix)\n const receivedSig = signature.startsWith('sha256=')\n ? signature.slice(7)\n : signature;\n\n // Compute expected signature\n const expectedSig = crypto\n .createHmac('sha256', secret)\n .update(payloadBuffer)\n .digest('hex');\n\n // Constant-time comparison\n try {\n return crypto.timingSafeEqual(\n Buffer.from(receivedSig),\n Buffer.from(expectedSig)\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Verify that a webhook timestamp is within acceptable range.\n *\n * @param timestamp - ISO 8601 timestamp string\n * @param toleranceSeconds - Maximum age in seconds (default: 300)\n * @returns true if timestamp is valid\n */\nexport function verifyTimestamp(\n timestamp: string,\n toleranceSeconds: number = 300\n): boolean {\n try {\n const webhookTime = new Date(timestamp).getTime();\n const now = Date.now();\n const ageMs = Math.abs(now - webhookTime);\n return ageMs <= toleranceSeconds * 1000;\n } catch {\n return false;\n }\n}\n\n/**\n * Verify a webhook and parse the event.\n *\n * This is the main function for processing incoming webhooks. It:\n * 1. Verifies the timestamp is not too old (replay protection)\n * 2. Verifies the HMAC signature\n * 3. Parses and returns the event\n *\n * @param options - Verification options\n * @returns Parsed and verified webhook event\n * @throws WebhookExpiredError if timestamp is too old\n * @throws WebhookSignatureError if signature is invalid\n * @throws WebhookVerificationError if payload cannot be parsed\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { verifyWebhook, WebhookVerificationError } from '@tritonium/api-client/webhooks';\n *\n * const app = express();\n * const WEBHOOK_SECRET = 'whsec_your_secret';\n *\n * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {\n * try {\n * const event = verifyWebhook({\n * payload: req.body,\n * signature: req.headers['x-tritonium-signature'] as string,\n * timestamp: req.headers['x-tritonium-timestamp'] as string,\n * secret: WEBHOOK_SECRET,\n * });\n *\n * switch (event.eventType) {\n * case 'review.received':\n * handleReview(event.data);\n * break;\n * case 'alert.triggered':\n * handleAlert(event.data);\n * break;\n * }\n *\n * res.json({ status: 'ok' });\n * } catch (e) {\n * if (e instanceof WebhookVerificationError) {\n * res.status(401).json({ error: e.message });\n * } else {\n * throw e;\n * }\n * }\n * });\n * ```\n */\nexport function verifyWebhook<T = Record<string, unknown>>(\n options: VerifyWebhookOptions\n): WebhookEvent<T> {\n const { payload, signature, timestamp, secret, toleranceSeconds = 300 } = options;\n\n // Check timestamp first (replay protection)\n if (!verifyTimestamp(timestamp, toleranceSeconds)) {\n throw new WebhookExpiredError(\n `Webhook timestamp is too old or invalid: ${timestamp}`\n );\n }\n\n // Verify signature\n if (!verifySignature(payload, signature, secret)) {\n throw new WebhookSignatureError('Invalid webhook signature');\n }\n\n // Parse payload\n let data: Record<string, unknown>;\n try {\n const payloadStr =\n typeof payload === 'string' ? payload : payload.toString('utf-8');\n data = JSON.parse(payloadStr);\n } catch (e) {\n throw new WebhookVerificationError(`Invalid webhook payload: ${e}`);\n }\n\n // Parse timestamp\n let eventTimestamp: Date;\n try {\n eventTimestamp = new Date(\n (data.timestamp as string) || timestamp\n );\n } catch {\n eventTimestamp = new Date();\n }\n\n return {\n eventId: data.event_id as string || '',\n eventType: data.event_type as string || '',\n timestamp: eventTimestamp,\n tenantId: data.tenant_id as string || '',\n appUuid: data.app_uuid as string | undefined,\n data: (data.data || {}) as T,\n rawPayload: data,\n };\n}\n\n/**\n * Alias for verifyWebhook for compatibility with other webhook libraries.\n */\nexport const constructEvent = verifyWebhook;\n\n/**\n * Type guard to check if an error is a WebhookVerificationError.\n */\nexport function isWebhookError(error: unknown): error is WebhookVerificationError {\n return error instanceof WebhookVerificationError;\n}\n\n/**\n * Type definitions for specific event payloads.\n */\nexport interface ReviewReceivedData {\n review_id: string;\n platform: string;\n rating: number;\n title?: string;\n text: string;\n author?: string;\n review_date: string;\n country?: string;\n version?: string;\n}\n\nexport interface AlertTriggeredData {\n alert_id: string;\n rule_id: string;\n rule_name: string;\n severity: 'low' | 'medium' | 'high' | 'critical';\n message: string;\n current_value?: number;\n threshold?: number;\n}\n\nexport interface CrisisDetectedData {\n severity: 'low' | 'medium' | 'high' | 'critical';\n type: string;\n message: string;\n affected_period?: string;\n metrics?: Record<string, unknown>;\n}\n"]}
@@ -0,0 +1,95 @@
1
+ import * as crypto from 'crypto';
2
+
3
+ // webhooks.ts
4
+ var WebhookVerificationError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "WebhookVerificationError";
8
+ }
9
+ };
10
+ var WebhookExpiredError = class extends WebhookVerificationError {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "WebhookExpiredError";
14
+ }
15
+ };
16
+ var WebhookSignatureError = class extends WebhookVerificationError {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "WebhookSignatureError";
20
+ }
21
+ };
22
+ var EventTypes = {
23
+ CRISIS_DETECTED: "crisis.detected",
24
+ INSIGHT_GENERATED: "insight.generated",
25
+ REVIEW_RECEIVED: "review.received",
26
+ ANALYSIS_COMPLETED: "analysis.completed",
27
+ ALERT_TRIGGERED: "alert.triggered",
28
+ TEST_PING: "test.ping"
29
+ };
30
+ function verifySignature(payload, signature, secret) {
31
+ const payloadBuffer = typeof payload === "string" ? Buffer.from(payload) : payload;
32
+ const receivedSig = signature.startsWith("sha256=") ? signature.slice(7) : signature;
33
+ const expectedSig = crypto.createHmac("sha256", secret).update(payloadBuffer).digest("hex");
34
+ try {
35
+ return crypto.timingSafeEqual(
36
+ Buffer.from(receivedSig),
37
+ Buffer.from(expectedSig)
38
+ );
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+ function verifyTimestamp(timestamp, toleranceSeconds = 300) {
44
+ try {
45
+ const webhookTime = new Date(timestamp).getTime();
46
+ const now = Date.now();
47
+ const ageMs = Math.abs(now - webhookTime);
48
+ return ageMs <= toleranceSeconds * 1e3;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+ function verifyWebhook(options) {
54
+ const { payload, signature, timestamp, secret, toleranceSeconds = 300 } = options;
55
+ if (!verifyTimestamp(timestamp, toleranceSeconds)) {
56
+ throw new WebhookExpiredError(
57
+ `Webhook timestamp is too old or invalid: ${timestamp}`
58
+ );
59
+ }
60
+ if (!verifySignature(payload, signature, secret)) {
61
+ throw new WebhookSignatureError("Invalid webhook signature");
62
+ }
63
+ let data;
64
+ try {
65
+ const payloadStr = typeof payload === "string" ? payload : payload.toString("utf-8");
66
+ data = JSON.parse(payloadStr);
67
+ } catch (e) {
68
+ throw new WebhookVerificationError(`Invalid webhook payload: ${e}`);
69
+ }
70
+ let eventTimestamp;
71
+ try {
72
+ eventTimestamp = new Date(
73
+ data.timestamp || timestamp
74
+ );
75
+ } catch {
76
+ eventTimestamp = /* @__PURE__ */ new Date();
77
+ }
78
+ return {
79
+ eventId: data.event_id || "",
80
+ eventType: data.event_type || "",
81
+ timestamp: eventTimestamp,
82
+ tenantId: data.tenant_id || "",
83
+ appUuid: data.app_uuid,
84
+ data: data.data || {},
85
+ rawPayload: data
86
+ };
87
+ }
88
+ var constructEvent = verifyWebhook;
89
+ function isWebhookError(error) {
90
+ return error instanceof WebhookVerificationError;
91
+ }
92
+
93
+ export { EventTypes, WebhookExpiredError, WebhookSignatureError, WebhookVerificationError, constructEvent, isWebhookError, verifySignature, verifyTimestamp, verifyWebhook };
94
+ //# sourceMappingURL=webhooks.mjs.map
95
+ //# sourceMappingURL=webhooks.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../webhooks.ts"],"names":[],"mappings":"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","file":"webhooks.mjs","sourcesContent":["/**\n * Webhook verification utilities for Tritonium.\n *\n * This module provides functions for verifying webhook signatures and handling\n * incoming webhook events from Tritonium.\n *\n * @example\n * ```typescript\n * import { verifyWebhook, WebhookEvent } from './webhooks';\n *\n * const event = verifyWebhook({\n * payload: requestBody,\n * signature: headers['x-tritonium-signature'],\n * timestamp: headers['x-tritonium-timestamp'],\n * secret: 'whsec_your_secret',\n * });\n * ```\n */\n\nimport * as crypto from 'crypto';\n\n/**\n * Error thrown when webhook verification fails.\n */\nexport class WebhookVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'WebhookVerificationError';\n }\n}\n\n/**\n * Error thrown when webhook timestamp is too old.\n */\nexport class WebhookExpiredError extends WebhookVerificationError {\n constructor(message: string) {\n super(message);\n this.name = 'WebhookExpiredError';\n }\n}\n\n/**\n * Error thrown when webhook signature is invalid.\n */\nexport class WebhookSignatureError extends WebhookVerificationError {\n constructor(message: string) {\n super(message);\n this.name = 'WebhookSignatureError';\n }\n}\n\n/**\n * Parsed webhook event from Tritonium.\n */\nexport interface WebhookEvent<T = Record<string, unknown>> {\n /** Unique identifier for this event */\n eventId: string;\n /** Type of event (e.g., 'review.received', 'alert.triggered') */\n eventType: string;\n /** When the event occurred */\n timestamp: Date;\n /** Tenant that owns this event */\n tenantId: string;\n /** Optional app UUID related to the event */\n appUuid?: string;\n /** Event-specific payload data */\n data: T;\n /** Original JSON payload */\n rawPayload: Record<string, unknown>;\n}\n\n/**\n * Tritonium webhook event types.\n */\nexport const EventTypes = {\n CRISIS_DETECTED: 'crisis.detected',\n INSIGHT_GENERATED: 'insight.generated',\n REVIEW_RECEIVED: 'review.received',\n ANALYSIS_COMPLETED: 'analysis.completed',\n ALERT_TRIGGERED: 'alert.triggered',\n TEST_PING: 'test.ping',\n} as const;\n\nexport type EventType = (typeof EventTypes)[keyof typeof EventTypes];\n\n/**\n * Options for webhook verification.\n */\nexport interface VerifyWebhookOptions {\n /** Raw request body (string or Buffer) */\n payload: string | Buffer;\n /** Value of X-Tritonium-Signature header */\n signature: string;\n /** Value of X-Tritonium-Timestamp header */\n timestamp: string;\n /** Your webhook signing secret */\n secret: string;\n /** Maximum age in seconds (default: 300 = 5 minutes) */\n toleranceSeconds?: number;\n}\n\n/**\n * Verify a webhook signature without parsing the payload.\n *\n * @param payload - Raw request body\n * @param signature - Value of X-Tritonium-Signature header\n * @param secret - Your webhook signing secret\n * @returns true if signature is valid\n */\nexport function verifySignature(\n payload: string | Buffer,\n signature: string,\n secret: string\n): boolean {\n const payloadBuffer =\n typeof payload === 'string' ? Buffer.from(payload) : payload;\n\n // Extract the hex signature (remove 'sha256=' prefix)\n const receivedSig = signature.startsWith('sha256=')\n ? signature.slice(7)\n : signature;\n\n // Compute expected signature\n const expectedSig = crypto\n .createHmac('sha256', secret)\n .update(payloadBuffer)\n .digest('hex');\n\n // Constant-time comparison\n try {\n return crypto.timingSafeEqual(\n Buffer.from(receivedSig),\n Buffer.from(expectedSig)\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Verify that a webhook timestamp is within acceptable range.\n *\n * @param timestamp - ISO 8601 timestamp string\n * @param toleranceSeconds - Maximum age in seconds (default: 300)\n * @returns true if timestamp is valid\n */\nexport function verifyTimestamp(\n timestamp: string,\n toleranceSeconds: number = 300\n): boolean {\n try {\n const webhookTime = new Date(timestamp).getTime();\n const now = Date.now();\n const ageMs = Math.abs(now - webhookTime);\n return ageMs <= toleranceSeconds * 1000;\n } catch {\n return false;\n }\n}\n\n/**\n * Verify a webhook and parse the event.\n *\n * This is the main function for processing incoming webhooks. It:\n * 1. Verifies the timestamp is not too old (replay protection)\n * 2. Verifies the HMAC signature\n * 3. Parses and returns the event\n *\n * @param options - Verification options\n * @returns Parsed and verified webhook event\n * @throws WebhookExpiredError if timestamp is too old\n * @throws WebhookSignatureError if signature is invalid\n * @throws WebhookVerificationError if payload cannot be parsed\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { verifyWebhook, WebhookVerificationError } from '@tritonium/api-client/webhooks';\n *\n * const app = express();\n * const WEBHOOK_SECRET = 'whsec_your_secret';\n *\n * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {\n * try {\n * const event = verifyWebhook({\n * payload: req.body,\n * signature: req.headers['x-tritonium-signature'] as string,\n * timestamp: req.headers['x-tritonium-timestamp'] as string,\n * secret: WEBHOOK_SECRET,\n * });\n *\n * switch (event.eventType) {\n * case 'review.received':\n * handleReview(event.data);\n * break;\n * case 'alert.triggered':\n * handleAlert(event.data);\n * break;\n * }\n *\n * res.json({ status: 'ok' });\n * } catch (e) {\n * if (e instanceof WebhookVerificationError) {\n * res.status(401).json({ error: e.message });\n * } else {\n * throw e;\n * }\n * }\n * });\n * ```\n */\nexport function verifyWebhook<T = Record<string, unknown>>(\n options: VerifyWebhookOptions\n): WebhookEvent<T> {\n const { payload, signature, timestamp, secret, toleranceSeconds = 300 } = options;\n\n // Check timestamp first (replay protection)\n if (!verifyTimestamp(timestamp, toleranceSeconds)) {\n throw new WebhookExpiredError(\n `Webhook timestamp is too old or invalid: ${timestamp}`\n );\n }\n\n // Verify signature\n if (!verifySignature(payload, signature, secret)) {\n throw new WebhookSignatureError('Invalid webhook signature');\n }\n\n // Parse payload\n let data: Record<string, unknown>;\n try {\n const payloadStr =\n typeof payload === 'string' ? payload : payload.toString('utf-8');\n data = JSON.parse(payloadStr);\n } catch (e) {\n throw new WebhookVerificationError(`Invalid webhook payload: ${e}`);\n }\n\n // Parse timestamp\n let eventTimestamp: Date;\n try {\n eventTimestamp = new Date(\n (data.timestamp as string) || timestamp\n );\n } catch {\n eventTimestamp = new Date();\n }\n\n return {\n eventId: data.event_id as string || '',\n eventType: data.event_type as string || '',\n timestamp: eventTimestamp,\n tenantId: data.tenant_id as string || '',\n appUuid: data.app_uuid as string | undefined,\n data: (data.data || {}) as T,\n rawPayload: data,\n };\n}\n\n/**\n * Alias for verifyWebhook for compatibility with other webhook libraries.\n */\nexport const constructEvent = verifyWebhook;\n\n/**\n * Type guard to check if an error is a WebhookVerificationError.\n */\nexport function isWebhookError(error: unknown): error is WebhookVerificationError {\n return error instanceof WebhookVerificationError;\n}\n\n/**\n * Type definitions for specific event payloads.\n */\nexport interface ReviewReceivedData {\n review_id: string;\n platform: string;\n rating: number;\n title?: string;\n text: string;\n author?: string;\n review_date: string;\n country?: string;\n version?: string;\n}\n\nexport interface AlertTriggeredData {\n alert_id: string;\n rule_id: string;\n rule_name: string;\n severity: 'low' | 'medium' | 'high' | 'critical';\n message: string;\n current_value?: number;\n threshold?: number;\n}\n\nexport interface CrisisDetectedData {\n severity: 'low' | 'medium' | 'high' | 'critical';\n type: string;\n message: string;\n affected_period?: string;\n metrics?: Record<string, unknown>;\n}\n"]}