@hookflo/tern 3.0.17-beta → 4.0.2

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,6 +8,7 @@ export declare class WebhookVerificationService {
8
8
  static verifyWithPlatformConfig<TPayload = unknown>(request: Request, platform: WebhookPlatform, secret: string, toleranceInSeconds?: number, normalize?: boolean | NormalizeOptions): Promise<WebhookVerificationResult<TPayload>>;
9
9
  static verifyAny<TPayload = unknown>(request: Request, secrets: MultiPlatformSecrets, toleranceInSeconds?: number, normalize?: boolean | NormalizeOptions): Promise<WebhookVerificationResult<TPayload>>;
10
10
  private static resolveCanonicalEventId;
11
+ private static pickString;
11
12
  private static resolveRawEventId;
12
13
  static detectPlatform(request: Request): WebhookPlatform;
13
14
  static getPlatformsUsingAlgorithm(algorithm: string): WebhookPlatform[];
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ class WebhookVerificationService {
48
48
  // Ensure the platform is set correctly in the result
49
49
  if (result.isValid) {
50
50
  result.platform = config.platform;
51
- result.eventId = this.resolveCanonicalEventId(config.platform, result.metadata);
51
+ result.eventId = this.resolveCanonicalEventId(config.platform, result.metadata, result.payload);
52
52
  if (config.normalize) {
53
53
  result.payload = (0, simple_1.normalizePayload)(config.platform, result.payload, config.normalize);
54
54
  }
@@ -132,16 +132,73 @@ class WebhookVerificationService {
132
132
  },
133
133
  };
134
134
  }
135
- static resolveCanonicalEventId(platform, metadata) {
136
- const rawId = this.resolveRawEventId(platform, metadata);
135
+ static resolveCanonicalEventId(platform, metadata, payload) {
136
+ const rawId = this.resolveRawEventId(platform, metadata, payload);
137
137
  return `${platform}_${rawId}`;
138
138
  }
139
- static resolveRawEventId(platform, metadata) {
140
- const candidate = metadata?.id || metadata?.delivery || metadata?.requestId || metadata?.timestamp;
141
- if (candidate !== undefined && candidate !== null && `${candidate}`.trim().length > 0) {
142
- return `${candidate}`.trim();
139
+ static pickString(...candidates) {
140
+ for (const candidate of candidates) {
141
+ if (candidate === undefined || candidate === null) {
142
+ continue;
143
+ }
144
+ const normalized = `${candidate}`.trim();
145
+ if (normalized.length > 0 && normalized !== 'undefined' && normalized !== 'null') {
146
+ return normalized;
147
+ }
143
148
  }
144
- return `generated-${Date.now().toString(36)}-${platform}`;
149
+ return undefined;
150
+ }
151
+ static resolveRawEventId(platform, metadata, payload) {
152
+ const candidate = (() => {
153
+ switch (platform) {
154
+ case 'stripe':
155
+ return this.pickString(payload?.request?.idempotency_key, payload?.id, metadata?.id);
156
+ case 'github':
157
+ return this.pickString(metadata?.delivery, metadata?.id, payload?.id);
158
+ case 'clerk':
159
+ return this.pickString(metadata?.id, payload?.id);
160
+ case 'shopify':
161
+ return this.pickString(metadata?.id, payload?.id);
162
+ case 'polar':
163
+ return this.pickString(payload?.data?.id, payload?.id, metadata?.id);
164
+ case 'dodopayments':
165
+ return this.pickString(payload?.data?.payment_id, payload?.data?.subscription_id, payload?.data?.id, metadata?.id);
166
+ case 'gitlab':
167
+ return this.pickString(payload?.object_attributes?.id, payload?.project?.id, metadata?.id);
168
+ case 'paddle':
169
+ return this.pickString(payload?.event_id, payload?.data?.id, metadata?.id);
170
+ case 'razorpay':
171
+ return this.pickString(payload?.payload?.payment?.entity?.id, payload?.payload?.order?.entity?.id, payload?.payload?.subscription?.entity?.id, payload?.id, metadata?.id);
172
+ case 'lemonsqueezy': {
173
+ const eventName = this.pickString(payload?.meta?.event_name);
174
+ const dataId = this.pickString(payload?.data?.id, payload?.id);
175
+ if (eventName && dataId) {
176
+ return `${eventName}_${dataId}`;
177
+ }
178
+ return this.pickString(dataId, metadata?.id);
179
+ }
180
+ case 'workos':
181
+ case 'vercel':
182
+ case 'replicateai':
183
+ case 'sentry':
184
+ return this.pickString(payload?.id, metadata?.id);
185
+ case 'woocommerce':
186
+ return this.pickString(payload?.id, metadata?.id);
187
+ case 'falai':
188
+ return this.pickString(payload?.request_id, metadata?.id);
189
+ case 'grafana':
190
+ return this.pickString(payload?.groupKey, payload?.alerts?.[0]?.fingerprint, metadata?.id);
191
+ case 'doppler':
192
+ return this.pickString(payload?.event?.id, metadata?.id);
193
+ case 'sanity':
194
+ return this.pickString(payload?.transactionId, payload?._id, metadata?.id);
195
+ default:
196
+ return this.pickString(payload?.idempotency_key, payload?.event_id, payload?.webhook_id, payload?.request_id, payload?.id, payload?.data?.id, metadata?.id, metadata?.delivery, metadata?.requestId);
197
+ }
198
+ })();
199
+ if (candidate)
200
+ return candidate;
201
+ return `generated-missing-${platform}`;
145
202
  }
146
203
  static detectPlatform(request) {
147
204
  const headers = request.headers;
@@ -157,7 +157,9 @@ async function resolveDeduplicationId(request, verificationResult) {
157
157
  headers.get('x-github-delivery') ||
158
158
  headers.get('idempotency-key') ||
159
159
  headers.get('upstash-deduplication-id') ||
160
- verificationResult.eventId ||
160
+ (verificationResult.eventId && !verificationResult.eventId.includes('generated-missing-')
161
+ ? verificationResult.eventId
162
+ : null) ||
161
163
  (typeof verificationResult.metadata?.id === 'string' ? verificationResult.metadata.id : null) ||
162
164
  payloadId ||
163
165
  payloadRequestId ||
@@ -183,7 +185,7 @@ async function handleReceive(request, platform, secret, queueConfig, toleranceIn
183
185
  metadata: verificationResult.metadata || {},
184
186
  };
185
187
  const deduplicationId = await resolveDeduplicationId(request, verificationResult);
186
- if (process.env.NODE_ENV !== 'production') {
188
+ if (process.env.NODE_ENV == 'production') {
187
189
  console.log(`[tern] deduplication-id: ${deduplicationId} (platform: ${platform})`);
188
190
  }
189
191
  const publishPayload = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hookflo/tern",
3
- "version": "3.0.17-beta",
3
+ "version": "4.0.2",
4
4
  "description": "A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",