@hookflo/tern 4.0.0 → 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 ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hookflo/tern",
3
- "version": "4.0.0",
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",