@entrolytics/webhooks 3.0.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/LICENSE +21 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +360 -0
- package/dist/index.js.map +1 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Entrolytics <hey@entrolytics.click>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared outbound webhook delivery used by BOTH the API and the worker so the SSRF guard, HMAC
|
|
3
|
+
* signing, retry schedule, and delivery state machine cannot drift between surfaces (JWE-03).
|
|
4
|
+
* Producer entry point is dispatchWebhookEvent; the worker drives durable retries via
|
|
5
|
+
* retryFailedDeliveries.
|
|
6
|
+
*/
|
|
7
|
+
export declare const WEBHOOK_EVENT_TYPES: readonly ['pageview.created', 'session.created', 'event.created', 'goal.completed', 'visitor.identified', 'revenue.recorded'];
|
|
8
|
+
export declare const WEBHOOK_EVENT_WILDCARD = "*";
|
|
9
|
+
export type WebhookEventType = (typeof WEBHOOK_EVENT_TYPES)[number];
|
|
10
|
+
export type WebhookSubscriptionEventType = WebhookEventType | typeof WEBHOOK_EVENT_WILDCARD;
|
|
11
|
+
export declare const MAX_WEBHOOK_ATTEMPTS: number;
|
|
12
|
+
/**
|
|
13
|
+
* Validate a webhook target URL against the SSRF policy. In production: HTTPS only, no credentials,
|
|
14
|
+
* no localhost/0.0.0.0, and no literal-or-DNS-resolved private network address.
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertWebhookTargetAllowed(rawUrl: string): Promise<URL>;
|
|
17
|
+
/** Generate the HMAC-SHA256 signature header value for an outbound payload. */
|
|
18
|
+
export declare function generateWebhookSignature(payload: string, secret: string): string;
|
|
19
|
+
/** Verify an inbound webhook signature (for receivers / tests). */
|
|
20
|
+
export declare function verifyWebhookSignature(payload: string, signature: string, secret: string, tolerance?: number): boolean;
|
|
21
|
+
export interface WebhookSendResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
retryable: boolean;
|
|
24
|
+
statusCode?: number;
|
|
25
|
+
response?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Send one signed webhook request through the SSRF guard with a bounded timeout. This is the ONLY
|
|
30
|
+
* outbound sender; both dispatch and retry route through it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sendWebhookRequest(url: string, payload: Record<string, unknown>, secret: string): Promise<WebhookSendResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Dispatch a webhook event to all subscribed, enabled endpoints. Creates a durable webhook_delivery
|
|
35
|
+
* row per target (so failures can be retried by the worker) and attempts immediate delivery. Call
|
|
36
|
+
* this from ingestion / domain events (JWE-02).
|
|
37
|
+
*/
|
|
38
|
+
export declare function dispatchWebhookEvent(eventType: WebhookEventType, data: Record<string, unknown>, websiteId?: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Drive due deliveries to a terminal state. Each row is CLAIMED atomically (status -> 'sending' via
|
|
41
|
+
* a conditional UPDATE) before the network call, so concurrent worker instances or overlapping
|
|
42
|
+
* ticks cannot double-send the same delivery (JWE-07). Idempotent and safe to run every minute.
|
|
43
|
+
*/
|
|
44
|
+
export declare function retryFailedDeliveries(batchSize?: number): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Send a SIGNED test event using the same envelope and sender as production dispatch, so
|
|
47
|
+
* signature-verifying receivers accept it (JWE-05).
|
|
48
|
+
*/
|
|
49
|
+
export declare function testWebhook(webhookId: string): Promise<WebhookSendResult>;
|
|
50
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AAEH,eAAO,MAAM,mBAAmB,YAC9B,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,CACV,CAAA;AAEV,eAAO,MAAM,sBAAsB,MAAM,CAAA;AAGzC,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AACnE,MAAM,MAAM,4BAA4B,GAAG,gBAAgB,GAAG,OAAO,sBAAsB,CAAA;AAI3F,eAAO,MAAM,oBAAoB,QAA0B,CAAA;AA0D3D;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAwC7E;AAID,+EAA+E;AAC/E,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAKhF;AAED,mEAAmE;AACnE,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,SAAM,GACd,OAAO,CAuBT;AAID,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,iBAAiB,CAAC,CAoD5B;AA+CD;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,gBAAgB,EAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAkDf;AAID;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0F1E;AAID;;;GAGG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoB/E"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { lookup } from 'node:dns/promises';
|
|
3
|
+
import { isIP } from 'node:net';
|
|
4
|
+
import { and, db, eq, isNull, lt, or, sql, webhook, webhookDelivery } from '@entrolytics/db';
|
|
5
|
+
/**
|
|
6
|
+
* Shared outbound webhook delivery used by BOTH the API and the worker so the SSRF guard, HMAC
|
|
7
|
+
* signing, retry schedule, and delivery state machine cannot drift between surfaces (JWE-03).
|
|
8
|
+
* Producer entry point is dispatchWebhookEvent; the worker drives durable retries via
|
|
9
|
+
* retryFailedDeliveries.
|
|
10
|
+
*/
|
|
11
|
+
export const WEBHOOK_EVENT_TYPES = [
|
|
12
|
+
'pageview.created',
|
|
13
|
+
'session.created',
|
|
14
|
+
'event.created',
|
|
15
|
+
'goal.completed',
|
|
16
|
+
'visitor.identified',
|
|
17
|
+
'revenue.recorded',
|
|
18
|
+
];
|
|
19
|
+
export const WEBHOOK_EVENT_WILDCARD = '*';
|
|
20
|
+
// Exponential backoff schedule (1min, 5min, 15min). Total attempts = initial + 3.
|
|
21
|
+
const RETRY_DELAYS = [60_000, 300_000, 900_000];
|
|
22
|
+
export const MAX_WEBHOOK_ATTEMPTS = RETRY_DELAYS.length + 1;
|
|
23
|
+
const WEBHOOK_TIMEOUT_MS = 30_000;
|
|
24
|
+
function isRetryableStatusCode(statusCode) {
|
|
25
|
+
return statusCode >= 500 || [408, 409, 425, 429].includes(statusCode);
|
|
26
|
+
}
|
|
27
|
+
function getNextRetryAt(attempts, maxAttempts) {
|
|
28
|
+
if (attempts >= maxAttempts) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const retryDelay = RETRY_DELAYS[Math.min(attempts - 1, RETRY_DELAYS.length - 1)];
|
|
32
|
+
return new Date(Date.now() + retryDelay);
|
|
33
|
+
}
|
|
34
|
+
// ---- SSRF guard ----------------------------------------------------------
|
|
35
|
+
function isPrivateIpv4(address) {
|
|
36
|
+
const parts = address.split('.').map((part) => Number.parseInt(part, 10));
|
|
37
|
+
if (parts.length !== 4 || parts.some((part) => Number.isNaN(part) || part < 0 || part > 255)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const [a, b] = parts;
|
|
41
|
+
return (a === 0 ||
|
|
42
|
+
a === 10 ||
|
|
43
|
+
a === 127 ||
|
|
44
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
45
|
+
(a === 169 && b === 254) ||
|
|
46
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
47
|
+
(a === 192 && b === 168));
|
|
48
|
+
}
|
|
49
|
+
function isPrivateIpv6(address) {
|
|
50
|
+
const normalized = address.toLowerCase();
|
|
51
|
+
if (normalized === '::' || normalized === '::1')
|
|
52
|
+
return true;
|
|
53
|
+
if (normalized.startsWith('::ffff:')) {
|
|
54
|
+
return isPrivateIpv4(normalized.replace('::ffff:', ''));
|
|
55
|
+
}
|
|
56
|
+
return (normalized.startsWith('fc') ||
|
|
57
|
+
normalized.startsWith('fd') ||
|
|
58
|
+
normalized.startsWith('fe8') ||
|
|
59
|
+
normalized.startsWith('fe9') ||
|
|
60
|
+
normalized.startsWith('fea') ||
|
|
61
|
+
normalized.startsWith('feb'));
|
|
62
|
+
}
|
|
63
|
+
function isPrivateAddress(address) {
|
|
64
|
+
const version = isIP(address);
|
|
65
|
+
if (version === 4)
|
|
66
|
+
return isPrivateIpv4(address);
|
|
67
|
+
if (version === 6)
|
|
68
|
+
return isPrivateIpv6(address);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate a webhook target URL against the SSRF policy. In production: HTTPS only, no credentials,
|
|
73
|
+
* no localhost/0.0.0.0, and no literal-or-DNS-resolved private network address.
|
|
74
|
+
*/
|
|
75
|
+
export async function assertWebhookTargetAllowed(rawUrl) {
|
|
76
|
+
const parsed = new URL(rawUrl);
|
|
77
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
78
|
+
throw new Error('Webhook URL must use HTTP or HTTPS');
|
|
79
|
+
}
|
|
80
|
+
if (parsed.username || parsed.password) {
|
|
81
|
+
throw new Error('Webhook URL must not include credentials');
|
|
82
|
+
}
|
|
83
|
+
const enforcePublicNetwork = process.env.NODE_ENV === 'production';
|
|
84
|
+
if (!enforcePublicNetwork) {
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
if (parsed.protocol !== 'https:') {
|
|
88
|
+
throw new Error('Webhook URL must use HTTPS in production');
|
|
89
|
+
}
|
|
90
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
91
|
+
if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '0.0.0.0') {
|
|
92
|
+
throw new Error('Webhook URL hostname is not allowed in production');
|
|
93
|
+
}
|
|
94
|
+
const literalIpVersion = isIP(hostname);
|
|
95
|
+
if (literalIpVersion !== 0 && isPrivateAddress(hostname)) {
|
|
96
|
+
throw new Error('Webhook URL points to a private network address');
|
|
97
|
+
}
|
|
98
|
+
if (literalIpVersion === 0) {
|
|
99
|
+
const records = await lookup(hostname, { all: true, order: 'verbatim' });
|
|
100
|
+
if (records.length === 0) {
|
|
101
|
+
throw new Error('Webhook URL hostname did not resolve to any IP address');
|
|
102
|
+
}
|
|
103
|
+
if (records.some((record) => isPrivateAddress(record.address))) {
|
|
104
|
+
throw new Error('Webhook URL resolves to a private network address');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
// ---- Signing -------------------------------------------------------------
|
|
110
|
+
/** Generate the HMAC-SHA256 signature header value for an outbound payload. */
|
|
111
|
+
export function generateWebhookSignature(payload, secret) {
|
|
112
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
113
|
+
const signedPayload = `${timestamp}.${payload}`;
|
|
114
|
+
const signature = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex');
|
|
115
|
+
return `t=${timestamp},v1=${signature}`;
|
|
116
|
+
}
|
|
117
|
+
/** Verify an inbound webhook signature (for receivers / tests). */
|
|
118
|
+
export function verifyWebhookSignature(payload, signature, secret, tolerance = 300) {
|
|
119
|
+
try {
|
|
120
|
+
const parts = signature.split(',');
|
|
121
|
+
const timestampPart = parts.find((p) => p.startsWith('t='));
|
|
122
|
+
const signaturePart = parts.find((p) => p.startsWith('v1='));
|
|
123
|
+
if (!timestampPart || !signaturePart)
|
|
124
|
+
return false;
|
|
125
|
+
const timestamp = Number.parseInt(timestampPart.replace('t=', ''), 10);
|
|
126
|
+
const expectedSignature = signaturePart.replace('v1=', '');
|
|
127
|
+
const now = Math.floor(Date.now() / 1000);
|
|
128
|
+
if (Math.abs(now - timestamp) > tolerance)
|
|
129
|
+
return false;
|
|
130
|
+
const signedPayload = `${timestamp}.${payload}`;
|
|
131
|
+
const computedSignature = crypto
|
|
132
|
+
.createHmac('sha256', secret)
|
|
133
|
+
.update(signedPayload)
|
|
134
|
+
.digest('hex');
|
|
135
|
+
return crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(computedSignature));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Send one signed webhook request through the SSRF guard with a bounded timeout. This is the ONLY
|
|
143
|
+
* outbound sender; both dispatch and retry route through it.
|
|
144
|
+
*/
|
|
145
|
+
export async function sendWebhookRequest(url, payload, secret) {
|
|
146
|
+
const payloadString = JSON.stringify(payload);
|
|
147
|
+
const signature = generateWebhookSignature(payloadString, secret);
|
|
148
|
+
let targetUrl;
|
|
149
|
+
try {
|
|
150
|
+
targetUrl = await assertWebhookTargetAllowed(url);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
return {
|
|
154
|
+
error: err instanceof Error ? err.message : 'Invalid webhook target URL',
|
|
155
|
+
retryable: false,
|
|
156
|
+
success: false,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const response = await fetch(targetUrl.toString(), {
|
|
161
|
+
body: payloadString,
|
|
162
|
+
headers: {
|
|
163
|
+
'Content-Type': 'application/json',
|
|
164
|
+
'User-Agent': 'Entrolytics-Webhook/1.0',
|
|
165
|
+
'X-Entrolytics-Signature': signature,
|
|
166
|
+
},
|
|
167
|
+
method: 'POST',
|
|
168
|
+
signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS),
|
|
169
|
+
});
|
|
170
|
+
const responseText = await response.text().catch(() => '');
|
|
171
|
+
if (response.ok) {
|
|
172
|
+
return {
|
|
173
|
+
response: responseText.slice(0, 1000),
|
|
174
|
+
retryable: false,
|
|
175
|
+
statusCode: response.status,
|
|
176
|
+
success: true,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
181
|
+
response: responseText.slice(0, 1000),
|
|
182
|
+
retryable: isRetryableStatusCode(response.status),
|
|
183
|
+
statusCode: response.status,
|
|
184
|
+
success: false,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
return {
|
|
189
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
190
|
+
retryable: true,
|
|
191
|
+
success: false,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// ---- Delivery store helpers ---------------------------------------------
|
|
196
|
+
async function getEnabledWebhooksForEvent(eventType, websiteId) {
|
|
197
|
+
if (websiteId) {
|
|
198
|
+
return db
|
|
199
|
+
.select()
|
|
200
|
+
.from(webhook)
|
|
201
|
+
.where(and(eq(webhook.websiteId, websiteId), or(sql `${eventType} = ANY(${webhook.events})`, sql `${WEBHOOK_EVENT_WILDCARD} = ANY(${webhook.events})`), eq(webhook.enabled, true), isNull(webhook.deletedAt)));
|
|
202
|
+
}
|
|
203
|
+
return db
|
|
204
|
+
.select()
|
|
205
|
+
.from(webhook)
|
|
206
|
+
.where(and(eq(webhook.enabled, true), or(sql `${eventType} = ANY(${webhook.events})`, sql `${WEBHOOK_EVENT_WILDCARD} = ANY(${webhook.events})`), isNull(webhook.deletedAt)));
|
|
207
|
+
}
|
|
208
|
+
function isWebhookPayload(value) {
|
|
209
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
210
|
+
}
|
|
211
|
+
function getWebhookPayload(value) {
|
|
212
|
+
if (isWebhookPayload(value))
|
|
213
|
+
return value;
|
|
214
|
+
throw new Error('Webhook payload must be a JSON object');
|
|
215
|
+
}
|
|
216
|
+
// ---- Producer ------------------------------------------------------------
|
|
217
|
+
/**
|
|
218
|
+
* Dispatch a webhook event to all subscribed, enabled endpoints. Creates a durable webhook_delivery
|
|
219
|
+
* row per target (so failures can be retried by the worker) and attempts immediate delivery. Call
|
|
220
|
+
* this from ingestion / domain events (JWE-02).
|
|
221
|
+
*/
|
|
222
|
+
export async function dispatchWebhookEvent(eventType, data, websiteId) {
|
|
223
|
+
const webhooks = await getEnabledWebhooksForEvent(eventType, websiteId);
|
|
224
|
+
if (webhooks.length === 0)
|
|
225
|
+
return;
|
|
226
|
+
const payload = {
|
|
227
|
+
created: new Date().toISOString(),
|
|
228
|
+
data,
|
|
229
|
+
id: crypto.randomUUID(),
|
|
230
|
+
type: eventType,
|
|
231
|
+
};
|
|
232
|
+
await Promise.all(webhooks.map(async (target) => {
|
|
233
|
+
const delivery = (await db
|
|
234
|
+
.insert(webhookDelivery)
|
|
235
|
+
.values({
|
|
236
|
+
attempts: 0,
|
|
237
|
+
eventType,
|
|
238
|
+
maxAttempts: MAX_WEBHOOK_ATTEMPTS,
|
|
239
|
+
payload,
|
|
240
|
+
status: 'pending',
|
|
241
|
+
webhookId: target.id,
|
|
242
|
+
})
|
|
243
|
+
.returning()).at(0);
|
|
244
|
+
if (!delivery)
|
|
245
|
+
return;
|
|
246
|
+
const result = await sendWebhookRequest(target.url, payload, target.secret);
|
|
247
|
+
const attempts = 1;
|
|
248
|
+
const maxAttempts = delivery.maxAttempts ?? MAX_WEBHOOK_ATTEMPTS;
|
|
249
|
+
const nextRetryAt = !result.success && result.retryable ? getNextRetryAt(attempts, maxAttempts) : null;
|
|
250
|
+
const shouldRetry = nextRetryAt !== null;
|
|
251
|
+
await db
|
|
252
|
+
.update(webhookDelivery)
|
|
253
|
+
.set({
|
|
254
|
+
attempts,
|
|
255
|
+
deliveredAt: result.success ? new Date() : null,
|
|
256
|
+
failedAt: result.success || shouldRetry ? null : new Date(),
|
|
257
|
+
nextRetryAt,
|
|
258
|
+
responseBody: result.response ?? (result.error ? `Error: ${result.error}` : null),
|
|
259
|
+
status: result.success ? 'success' : shouldRetry ? 'retrying' : 'failed',
|
|
260
|
+
statusCode: result.statusCode,
|
|
261
|
+
})
|
|
262
|
+
.where(eq(webhookDelivery.id, delivery.id));
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
// ---- Retry pump (worker) -------------------------------------------------
|
|
266
|
+
/**
|
|
267
|
+
* Drive due deliveries to a terminal state. Each row is CLAIMED atomically (status -> 'sending' via
|
|
268
|
+
* a conditional UPDATE) before the network call, so concurrent worker instances or overlapping
|
|
269
|
+
* ticks cannot double-send the same delivery (JWE-07). Idempotent and safe to run every minute.
|
|
270
|
+
*/
|
|
271
|
+
export async function retryFailedDeliveries(batchSize = 100) {
|
|
272
|
+
const now = new Date();
|
|
273
|
+
// Select candidates: pending/retrying and due.
|
|
274
|
+
const candidates = await db
|
|
275
|
+
.select({ id: webhookDelivery.id })
|
|
276
|
+
.from(webhookDelivery)
|
|
277
|
+
.where(and(or(eq(webhookDelivery.status, 'pending'), eq(webhookDelivery.status, 'retrying')), or(isNull(webhookDelivery.nextRetryAt), lt(webhookDelivery.nextRetryAt, now))))
|
|
278
|
+
.limit(batchSize);
|
|
279
|
+
for (const candidate of candidates) {
|
|
280
|
+
// Atomically claim: only one worker wins the transition out of pending/retrying.
|
|
281
|
+
const claimed = await db
|
|
282
|
+
.update(webhookDelivery)
|
|
283
|
+
.set({ status: 'sending' })
|
|
284
|
+
.where(and(eq(webhookDelivery.id, candidate.id), or(eq(webhookDelivery.status, 'pending'), eq(webhookDelivery.status, 'retrying'))))
|
|
285
|
+
.returning();
|
|
286
|
+
const delivery = claimed.at(0);
|
|
287
|
+
if (!delivery)
|
|
288
|
+
continue; // lost the race to another worker
|
|
289
|
+
const target = (await db.select().from(webhook).where(eq(webhook.id, delivery.webhookId)).limit(1)).at(0);
|
|
290
|
+
if (!target?.active || !target.enabled || target.deletedAt) {
|
|
291
|
+
await db
|
|
292
|
+
.update(webhookDelivery)
|
|
293
|
+
.set({
|
|
294
|
+
failedAt: new Date(),
|
|
295
|
+
nextRetryAt: null,
|
|
296
|
+
responseBody: 'Webhook disabled or deleted',
|
|
297
|
+
status: 'failed',
|
|
298
|
+
})
|
|
299
|
+
.where(eq(webhookDelivery.id, delivery.id));
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const maxAttempts = delivery.maxAttempts ?? MAX_WEBHOOK_ATTEMPTS;
|
|
303
|
+
if ((delivery.attempts ?? 0) >= maxAttempts) {
|
|
304
|
+
await db
|
|
305
|
+
.update(webhookDelivery)
|
|
306
|
+
.set({
|
|
307
|
+
failedAt: new Date(),
|
|
308
|
+
nextRetryAt: null,
|
|
309
|
+
responseBody: 'Max retry attempts reached',
|
|
310
|
+
status: 'failed',
|
|
311
|
+
})
|
|
312
|
+
.where(eq(webhookDelivery.id, delivery.id));
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const result = await sendWebhookRequest(target.url, getWebhookPayload(delivery.payload), target.secret);
|
|
316
|
+
const newAttempts = (delivery.attempts ?? 0) + 1;
|
|
317
|
+
const nextRetryAt = !result.success && result.retryable ? getNextRetryAt(newAttempts, maxAttempts) : null;
|
|
318
|
+
const shouldRetry = nextRetryAt !== null;
|
|
319
|
+
await db
|
|
320
|
+
.update(webhookDelivery)
|
|
321
|
+
.set({
|
|
322
|
+
attempts: newAttempts,
|
|
323
|
+
deliveredAt: result.success ? new Date() : null,
|
|
324
|
+
failedAt: result.success || shouldRetry ? null : new Date(),
|
|
325
|
+
nextRetryAt,
|
|
326
|
+
responseBody: result.success
|
|
327
|
+
? (result.response ?? null)
|
|
328
|
+
: result.error
|
|
329
|
+
? `${result.error}\n\n${result.response ?? ''}`
|
|
330
|
+
: (result.response ?? null),
|
|
331
|
+
status: result.success ? 'success' : shouldRetry ? 'retrying' : 'failed',
|
|
332
|
+
statusCode: result.statusCode,
|
|
333
|
+
})
|
|
334
|
+
.where(eq(webhookDelivery.id, delivery.id));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// ---- Test ----------------------------------------------------------------
|
|
338
|
+
/**
|
|
339
|
+
* Send a SIGNED test event using the same envelope and sender as production dispatch, so
|
|
340
|
+
* signature-verifying receivers accept it (JWE-05).
|
|
341
|
+
*/
|
|
342
|
+
export async function testWebhook(webhookId) {
|
|
343
|
+
const target = (await db.select().from(webhook).where(eq(webhook.id, webhookId)).limit(1)).at(0);
|
|
344
|
+
if (!target) {
|
|
345
|
+
return { error: 'Webhook not found', retryable: false, success: false };
|
|
346
|
+
}
|
|
347
|
+
const testPayload = {
|
|
348
|
+
created: new Date().toISOString(),
|
|
349
|
+
data: {
|
|
350
|
+
message: 'This is a test webhook from Entrolytics',
|
|
351
|
+
webhookId: target.id,
|
|
352
|
+
webhookName: target.name,
|
|
353
|
+
websiteId: target.websiteId,
|
|
354
|
+
},
|
|
355
|
+
id: crypto.randomUUID(),
|
|
356
|
+
type: 'test',
|
|
357
|
+
};
|
|
358
|
+
return sendWebhookRequest(target.url, testPayload, target.secret);
|
|
359
|
+
}
|
|
360
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@entrolytics/webhooks",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Shared outbound webhook delivery (SSRF-guarded sender, HMAC signing, retry state machine) for Entrolytics",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"!dist/*.tsbuildinfo",
|
|
8
|
+
"package.json"
|
|
9
|
+
],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@entrolytics/db": "^3.0.0",
|
|
24
|
+
"drizzle-orm": "rc5"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^26.5.0",
|
|
28
|
+
"typescript": "next"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"check-types": "tsc --noEmit"
|
|
33
|
+
}
|
|
34
|
+
}
|