@cross-deck/node 1.3.1 → 1.5.1
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/CHANGELOG.md +101 -0
- package/README.md +120 -0
- package/dist/auto-events/index.d.mts +1 -1
- package/dist/auto-events/index.d.ts +1 -1
- package/dist/contracts.json +552 -0
- package/dist/{crossdeck-server-DhnHvUhh.d.mts → crossdeck-server-CY4PZk-j.d.mts} +200 -12
- package/dist/{crossdeck-server-DhnHvUhh.d.ts → crossdeck-server-CY4PZk-j.d.ts} +200 -12
- package/dist/index.cjs +1020 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +150 -21
- package/dist/index.d.ts +150 -21
- package/dist/index.mjs +1005 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AliasIdentityInput, a as AliasResult, b as AuditDecision, c as AuditEntry, B as Breadcrumb, d as BreadcrumbCategory, e as BreadcrumbLevel, C as CROSSDECK_API_VERSION, f as CapturedError, g as
|
|
1
|
+
export { A as AliasIdentityInput, a as AliasResult, b as AuditDecision, c as AuditEntry, B as Breadcrumb, d as BreadcrumbCategory, e as BreadcrumbLevel, C as CROSSDECK_API_VERSION, f as CapturedError, g as Contract, h as ContractAppliesTo, i as ContractFailureInput, j as ContractPillar, k as ContractStatus, l as ContractTestRef, m as CrossdeckAuthenticationError, n as CrossdeckConfigurationError, o as CrossdeckContracts, p as CrossdeckError, q as CrossdeckErrorPayload, r as CrossdeckErrorType, s as CrossdeckInternalError, t as CrossdeckNetworkError, u as CrossdeckPermissionError, v as CrossdeckRateLimitError, w as CrossdeckServer, x as CrossdeckServerOptions, y as CrossdeckValidationError, D as DEFAULT_BASE_URL, z as DEFAULT_TIMEOUT_MS, E as Diagnostics, F as EntitlementCacheOptions, G as EntitlementMutationResult, H as EntitlementStore, I as EntitlementsListResponse, J as EntitlementsListener, K as Environment, L as ErrorCaptureConfig, M as ErrorLevel, N as EventProperties, O as ForgetResult, P as GrantDuration, Q as GrantEntitlementInput, R as GroupMembership, S as HeartbeatResponse, T as HttpRequestInfo, U as HttpResponseInfo, V as HttpRetriesConfig, W as IdentifyOptions, X as IdentityHints, Y as IngestOptions, Z as IngestResponse, _ as PublicEntitlement, $ as PurchaseResult, a0 as RequestOptions, a1 as RevokeEntitlementInput, a2 as RuntimeHost, a3 as RuntimeInfo, a4 as ServerEvent, a5 as StackFrame, a6 as StoredEntitlements, a7 as SyncPurchaseInput, a8 as makeCrossdeckError } from './crossdeck-server-CY4PZk-j.mjs';
|
|
2
2
|
import 'node:events';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -17,7 +17,7 @@ import 'node:events';
|
|
|
17
17
|
*
|
|
18
18
|
* Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
|
|
19
19
|
*/
|
|
20
|
-
declare const SDK_VERSION = "1.
|
|
20
|
+
declare const SDK_VERSION = "1.5.1";
|
|
21
21
|
declare const SDK_NAME = "@cross-deck/node";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -158,16 +158,34 @@ declare const _CROSSDECK_ERROR_CODES: readonly [{
|
|
|
158
158
|
readonly resolution: "Increase flushOnExitTimeoutMs in CrossdeckServer options. Default is 2000ms; serverless runtimes typically allow 5-10s before SIGKILL. If events are dropping silently in production, raise this.";
|
|
159
159
|
readonly retryable: false;
|
|
160
160
|
}, {
|
|
161
|
-
readonly code: "
|
|
161
|
+
readonly code: "webhook_signature_mismatch";
|
|
162
162
|
readonly type: "authentication_error";
|
|
163
|
-
readonly description: "
|
|
164
|
-
readonly resolution: "Confirm the secret matches
|
|
163
|
+
readonly description: "Webhook HMAC didn't verify against any configured secret (wrong-secret / stale rotation signal).";
|
|
164
|
+
readonly resolution: "Confirm the secret matches dashboard → Webhooks. If you rotated, include both the old and new secret as an array until receivers cut over.";
|
|
165
165
|
readonly retryable: false;
|
|
166
166
|
}, {
|
|
167
|
-
readonly code: "
|
|
167
|
+
readonly code: "webhook_timestamp_outside_tolerance";
|
|
168
168
|
readonly type: "authentication_error";
|
|
169
|
-
readonly description: "
|
|
170
|
-
readonly resolution: "
|
|
169
|
+
readonly description: "Webhook timestamp drift exceeds the configured replay-tolerance window (default 5 minutes; replay-attack signal).";
|
|
170
|
+
readonly resolution: "Verify NTP on the receiving host. A spike on this code warrants its own alert separate from signature_mismatch — replay attacks look like this.";
|
|
171
|
+
readonly retryable: false;
|
|
172
|
+
}, {
|
|
173
|
+
readonly code: "webhook_timestamp_missing";
|
|
174
|
+
readonly type: "authentication_error";
|
|
175
|
+
readonly description: "Webhook signature header is absent or has no `t=` timestamp segment — the timestamp gate cannot be verified.";
|
|
176
|
+
readonly resolution: "Confirm the request actually came from Crossdeck (signature headers are always present on real deliveries). A missing header is either a misconfigured intermediary or a forged request.";
|
|
177
|
+
readonly retryable: false;
|
|
178
|
+
}, {
|
|
179
|
+
readonly code: "webhook_payload_not_json";
|
|
180
|
+
readonly type: "authentication_error";
|
|
181
|
+
readonly description: "Webhook signature verified but the body isn't valid JSON — payload tampered post-signing or source bug.";
|
|
182
|
+
readonly resolution: "Inspect the raw payload. If it's not JSON, either the request was modified in transit or the sender has a bug — file a support ticket with the raw body.";
|
|
183
|
+
readonly retryable: false;
|
|
184
|
+
}, {
|
|
185
|
+
readonly code: "webhook_invalid_tolerance";
|
|
186
|
+
readonly type: "configuration_error";
|
|
187
|
+
readonly description: "verifyWebhookSignature() called with a non-finite / negative / above-24h-cap replayToleranceMs (would silently disable replay protection).";
|
|
188
|
+
readonly resolution: "Pass a finite number between 0 and 86_400_000ms (24h). Default (5 minutes) is correct for almost every scenario. Pre-v1.4.0 accepted Infinity/NaN and silently dropped the check.";
|
|
171
189
|
readonly retryable: false;
|
|
172
190
|
}, {
|
|
173
191
|
readonly code: "webhook_missing_secret";
|
|
@@ -175,6 +193,84 @@ declare const _CROSSDECK_ERROR_CODES: readonly [{
|
|
|
175
193
|
readonly description: "verifyWebhookSignature() was called without a signing secret.";
|
|
176
194
|
readonly resolution: "Pass the secret from your Crossdeck dashboard → Webhooks page. Never hardcode in source — read from an env var.";
|
|
177
195
|
readonly retryable: false;
|
|
196
|
+
}, {
|
|
197
|
+
readonly code: "webhook_invalid_signature";
|
|
198
|
+
readonly type: "authentication_error";
|
|
199
|
+
readonly description: "DEPRECATED in v1.4.0 — split into webhook_signature_mismatch / webhook_timestamp_missing / webhook_timestamp_outside_tolerance / webhook_payload_not_json for alerting clarity.";
|
|
200
|
+
readonly resolution: "Migrate alert rules to the more specific v1.4.0 codes — they distinguish replay-attack signals from wrong-secret signals.";
|
|
201
|
+
readonly retryable: false;
|
|
202
|
+
}, {
|
|
203
|
+
readonly code: "webhook_replay_window_exceeded";
|
|
204
|
+
readonly type: "authentication_error";
|
|
205
|
+
readonly description: "DEPRECATED in v1.4.0 — renamed to webhook_timestamp_outside_tolerance.";
|
|
206
|
+
readonly resolution: "Update alerts to webhook_timestamp_outside_tolerance.";
|
|
207
|
+
readonly retryable: false;
|
|
208
|
+
}, {
|
|
209
|
+
readonly code: "missing_api_key";
|
|
210
|
+
readonly type: "authentication_error";
|
|
211
|
+
readonly description: "No Authorization header (or Crossdeck-Api-Key header) on the request.";
|
|
212
|
+
readonly resolution: "Confirm the CrossdeckServer was constructed with a cd_sk_… secretKey. Re-check env vars in production deployments.";
|
|
213
|
+
readonly retryable: false;
|
|
214
|
+
}, {
|
|
215
|
+
readonly code: "invalid_api_key";
|
|
216
|
+
readonly type: "authentication_error";
|
|
217
|
+
readonly description: "The secret key is malformed, unknown, or doesn't resolve to a project.";
|
|
218
|
+
readonly resolution: "Copy the key from Crossdeck dashboard → API keys. Server SDK requires cd_sk_test_ / cd_sk_live_ — client SDK keys (cd_pub_…) won't work on the Node SDK.";
|
|
219
|
+
readonly retryable: false;
|
|
220
|
+
}, {
|
|
221
|
+
readonly code: "key_revoked";
|
|
222
|
+
readonly type: "authentication_error";
|
|
223
|
+
readonly description: "The secret key was revoked in the dashboard.";
|
|
224
|
+
readonly resolution: "Mint a fresh key in dashboard → API keys → Create new. The revoked key cannot be reactivated.";
|
|
225
|
+
readonly retryable: false;
|
|
226
|
+
}, {
|
|
227
|
+
readonly code: "env_mismatch";
|
|
228
|
+
readonly type: "permission_error";
|
|
229
|
+
readonly description: "The key's env prefix doesn't match the resolved app's configured env.";
|
|
230
|
+
readonly resolution: "Use a cd_sk_live_ key with a production app, cd_sk_test_ with a sandbox app. Crossing breaks the env lock.";
|
|
231
|
+
readonly retryable: false;
|
|
232
|
+
}, {
|
|
233
|
+
readonly code: "idempotency_key_in_use";
|
|
234
|
+
readonly type: "invalid_request_error";
|
|
235
|
+
readonly description: "An Idempotency-Key was reused for a request with a different body (Stripe-grade contract).";
|
|
236
|
+
readonly resolution: "Server SDK derives keys deterministically from the body since v1.4.0; this should only fire if you passed options.idempotencyKey explicitly. Use a fresh key per logical operation.";
|
|
237
|
+
readonly retryable: false;
|
|
238
|
+
}, {
|
|
239
|
+
readonly code: "rate_limited";
|
|
240
|
+
readonly type: "rate_limit_error";
|
|
241
|
+
readonly description: "Request rate exceeded the project's per-second cap.";
|
|
242
|
+
readonly resolution: "Honour Retry-After (managed retries do this automatically). For custom paths, throttle to <100 req/s/key.";
|
|
243
|
+
readonly retryable: true;
|
|
244
|
+
}, {
|
|
245
|
+
readonly code: "internal_error";
|
|
246
|
+
readonly type: "internal_error";
|
|
247
|
+
readonly description: "Server-side issue. Safe to retry with backoff.";
|
|
248
|
+
readonly resolution: "Managed retries handle this automatically. If a code path surfaces it to your code, contact support with the requestId.";
|
|
249
|
+
readonly retryable: true;
|
|
250
|
+
}, {
|
|
251
|
+
readonly code: "google_not_supported";
|
|
252
|
+
readonly type: "invalid_request_error";
|
|
253
|
+
readonly description: "POST /purchases/sync with rail=google is gated until the Play Developer API reconciliation worker ships.";
|
|
254
|
+
readonly resolution: "Until v1.5+, Google Play purchases verify via Real-time Developer Notifications. The Android SDK auto-track path handles this transparently.";
|
|
255
|
+
readonly retryable: false;
|
|
256
|
+
}, {
|
|
257
|
+
readonly code: "stripe_not_supported";
|
|
258
|
+
readonly type: "invalid_request_error";
|
|
259
|
+
readonly description: "POST /purchases/sync with rail=stripe is unsupported — Stripe webhooks deliver evidence server-side.";
|
|
260
|
+
readonly resolution: "Wire Stripe via the standard Checkout / Customer Portal flow; Crossdeck reconciles via the platform webhook automatically.";
|
|
261
|
+
readonly retryable: false;
|
|
262
|
+
}, {
|
|
263
|
+
readonly code: "missing_required_param";
|
|
264
|
+
readonly type: "invalid_request_error";
|
|
265
|
+
readonly description: "A required field is absent from the request body.";
|
|
266
|
+
readonly resolution: "The error.message identifies the missing field. Refer to the SDK's TypeScript types for canonical shapes.";
|
|
267
|
+
readonly retryable: false;
|
|
268
|
+
}, {
|
|
269
|
+
readonly code: "invalid_param_value";
|
|
270
|
+
readonly type: "invalid_request_error";
|
|
271
|
+
readonly description: "A field is present but the value failed validation.";
|
|
272
|
+
readonly resolution: "Read error.message for the field + reason. SDK-managed call sites should never emit this — file a bug if you do.";
|
|
273
|
+
readonly retryable: false;
|
|
178
274
|
}];
|
|
179
275
|
/**
|
|
180
276
|
* Literal union of every code documented in `CROSSDECK_ERROR_CODES`.
|
|
@@ -201,9 +297,22 @@ declare function getErrorCode(code: string): ErrorCodeEntry | undefined;
|
|
|
201
297
|
/**
|
|
202
298
|
* Webhook signature verification — Stripe pattern.
|
|
203
299
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
300
|
+
* **[ROADMAP — v1.4.0 honesty note]:** Crossdeck does NOT yet send
|
|
301
|
+
* outbound webhooks. Outbound delivery (signer + worker + scheduler
|
|
302
|
+
* + dead-letter dashboard) is on the post-v1.5 roadmap. This
|
|
303
|
+
* verifier exists today so customer-side integration code can be
|
|
304
|
+
* written and tested against fixtures (use `signWebhookPayload`
|
|
305
|
+
* to produce signed bodies for your local tests), and so the
|
|
306
|
+
* verification contract surface is locked in BEFORE delivery
|
|
307
|
+
* ships — Phase 7.2 of the bank-grade reconciliation tightened
|
|
308
|
+
* the timestamp-validation footguns here precisely because the
|
|
309
|
+
* helper IS the contract surface for inbound validation,
|
|
310
|
+
* regardless of when first-party delivery lights up.
|
|
311
|
+
*
|
|
312
|
+
* Lets customers verify the events Crossdeck sends to THEM (when
|
|
313
|
+
* delivery ships). Table-stakes for any backend SDK (Stripe ships
|
|
314
|
+
* `Stripe.webhooks.constructEvent()` from day one, Svix ships
|
|
315
|
+
* `Webhook.verify()` from day one).
|
|
207
316
|
*
|
|
208
317
|
* Wire format:
|
|
209
318
|
* Header `Crossdeck-Signature: t=<unix-seconds>,v1=<hex>`
|
|
@@ -244,9 +353,21 @@ declare function getErrorCode(code: string): ErrorCodeEntry | undefined;
|
|
|
244
353
|
interface VerifyWebhookOptions {
|
|
245
354
|
/**
|
|
246
355
|
* Maximum age of the webhook timestamp in milliseconds. Default
|
|
247
|
-
* 5 minutes (
|
|
248
|
-
* as a replay.
|
|
249
|
-
*
|
|
356
|
+
* 5 minutes (`DEFAULT_REPLAY_TOLERANCE_MS`). Anything older than
|
|
357
|
+
* this is rejected as a replay.
|
|
358
|
+
*
|
|
359
|
+
* **v1.4.0 Phase 7.2 bank-grade contract:** the timestamp window
|
|
360
|
+
* is MANDATORY. Pre-v1.4.0 the helper accepted `tolerance: 0`
|
|
361
|
+
* (silently disables the check) and `tolerance: Infinity` /
|
|
362
|
+
* `null` / `NaN` (silently disables via `Math.abs(...) > Infinity
|
|
363
|
+
* = false`). Customers relying on replay protection silently
|
|
364
|
+
* lost it.
|
|
365
|
+
*
|
|
366
|
+
* The helper now rejects non-finite / negative / above-cap
|
|
367
|
+
* tolerances at the boundary with a typed
|
|
368
|
+
* `webhook_invalid_tolerance` error. Hard upper bound is 24h —
|
|
369
|
+
* sufficient for any plausible clock-skew scenario, prevents
|
|
370
|
+
* "Infinity by typo" from defeating replay protection.
|
|
250
371
|
*/
|
|
251
372
|
replayToleranceMs?: number;
|
|
252
373
|
/**
|
|
@@ -257,13 +378,21 @@ interface VerifyWebhookOptions {
|
|
|
257
378
|
}
|
|
258
379
|
/**
|
|
259
380
|
* Verify a Crossdeck-signed webhook. Returns the parsed JSON payload
|
|
260
|
-
* on success. Throws `CrossdeckError`
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* -
|
|
265
|
-
* -
|
|
266
|
-
*
|
|
381
|
+
* on success. Throws `CrossdeckError` with one of these
|
|
382
|
+
* distinguishable codes (v1.4.0 Phase 7.2 — pre-v1.4.0 conflated
|
|
383
|
+
* everything under `webhook_invalid_signature`; alerting can now
|
|
384
|
+
* separate replay-attack signals from wrong-secret signals):
|
|
385
|
+
* - `webhook_missing_secret` — no secret configured.
|
|
386
|
+
* - `webhook_invalid_tolerance` — caller passed Infinity / NaN /
|
|
387
|
+
* negative / above-24h-cap `replayToleranceMs`.
|
|
388
|
+
* - `webhook_timestamp_missing` — header absent or has no `t=`.
|
|
389
|
+
* - `webhook_timestamp_outside_tolerance` — drift > tolerance
|
|
390
|
+
* (replay-attack signal — split this from signature mismatch
|
|
391
|
+
* in your alerting rules).
|
|
392
|
+
* - `webhook_signature_mismatch` — HMAC didn't match any
|
|
393
|
+
* configured secret (wrong-secret / rotation-drift signal).
|
|
394
|
+
* - `webhook_payload_not_json` — signature verified but the body
|
|
395
|
+
* isn't parseable JSON (tampered post-signing or source bug).
|
|
267
396
|
*
|
|
268
397
|
* `secret` accepts a single string or an array of strings (for
|
|
269
398
|
* rotation). Any one match is sufficient.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AliasIdentityInput, a as AliasResult, b as AuditDecision, c as AuditEntry, B as Breadcrumb, d as BreadcrumbCategory, e as BreadcrumbLevel, C as CROSSDECK_API_VERSION, f as CapturedError, g as
|
|
1
|
+
export { A as AliasIdentityInput, a as AliasResult, b as AuditDecision, c as AuditEntry, B as Breadcrumb, d as BreadcrumbCategory, e as BreadcrumbLevel, C as CROSSDECK_API_VERSION, f as CapturedError, g as Contract, h as ContractAppliesTo, i as ContractFailureInput, j as ContractPillar, k as ContractStatus, l as ContractTestRef, m as CrossdeckAuthenticationError, n as CrossdeckConfigurationError, o as CrossdeckContracts, p as CrossdeckError, q as CrossdeckErrorPayload, r as CrossdeckErrorType, s as CrossdeckInternalError, t as CrossdeckNetworkError, u as CrossdeckPermissionError, v as CrossdeckRateLimitError, w as CrossdeckServer, x as CrossdeckServerOptions, y as CrossdeckValidationError, D as DEFAULT_BASE_URL, z as DEFAULT_TIMEOUT_MS, E as Diagnostics, F as EntitlementCacheOptions, G as EntitlementMutationResult, H as EntitlementStore, I as EntitlementsListResponse, J as EntitlementsListener, K as Environment, L as ErrorCaptureConfig, M as ErrorLevel, N as EventProperties, O as ForgetResult, P as GrantDuration, Q as GrantEntitlementInput, R as GroupMembership, S as HeartbeatResponse, T as HttpRequestInfo, U as HttpResponseInfo, V as HttpRetriesConfig, W as IdentifyOptions, X as IdentityHints, Y as IngestOptions, Z as IngestResponse, _ as PublicEntitlement, $ as PurchaseResult, a0 as RequestOptions, a1 as RevokeEntitlementInput, a2 as RuntimeHost, a3 as RuntimeInfo, a4 as ServerEvent, a5 as StackFrame, a6 as StoredEntitlements, a7 as SyncPurchaseInput, a8 as makeCrossdeckError } from './crossdeck-server-CY4PZk-j.js';
|
|
2
2
|
import 'node:events';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -17,7 +17,7 @@ import 'node:events';
|
|
|
17
17
|
*
|
|
18
18
|
* Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
|
|
19
19
|
*/
|
|
20
|
-
declare const SDK_VERSION = "1.
|
|
20
|
+
declare const SDK_VERSION = "1.5.1";
|
|
21
21
|
declare const SDK_NAME = "@cross-deck/node";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -158,16 +158,34 @@ declare const _CROSSDECK_ERROR_CODES: readonly [{
|
|
|
158
158
|
readonly resolution: "Increase flushOnExitTimeoutMs in CrossdeckServer options. Default is 2000ms; serverless runtimes typically allow 5-10s before SIGKILL. If events are dropping silently in production, raise this.";
|
|
159
159
|
readonly retryable: false;
|
|
160
160
|
}, {
|
|
161
|
-
readonly code: "
|
|
161
|
+
readonly code: "webhook_signature_mismatch";
|
|
162
162
|
readonly type: "authentication_error";
|
|
163
|
-
readonly description: "
|
|
164
|
-
readonly resolution: "Confirm the secret matches
|
|
163
|
+
readonly description: "Webhook HMAC didn't verify against any configured secret (wrong-secret / stale rotation signal).";
|
|
164
|
+
readonly resolution: "Confirm the secret matches dashboard → Webhooks. If you rotated, include both the old and new secret as an array until receivers cut over.";
|
|
165
165
|
readonly retryable: false;
|
|
166
166
|
}, {
|
|
167
|
-
readonly code: "
|
|
167
|
+
readonly code: "webhook_timestamp_outside_tolerance";
|
|
168
168
|
readonly type: "authentication_error";
|
|
169
|
-
readonly description: "
|
|
170
|
-
readonly resolution: "
|
|
169
|
+
readonly description: "Webhook timestamp drift exceeds the configured replay-tolerance window (default 5 minutes; replay-attack signal).";
|
|
170
|
+
readonly resolution: "Verify NTP on the receiving host. A spike on this code warrants its own alert separate from signature_mismatch — replay attacks look like this.";
|
|
171
|
+
readonly retryable: false;
|
|
172
|
+
}, {
|
|
173
|
+
readonly code: "webhook_timestamp_missing";
|
|
174
|
+
readonly type: "authentication_error";
|
|
175
|
+
readonly description: "Webhook signature header is absent or has no `t=` timestamp segment — the timestamp gate cannot be verified.";
|
|
176
|
+
readonly resolution: "Confirm the request actually came from Crossdeck (signature headers are always present on real deliveries). A missing header is either a misconfigured intermediary or a forged request.";
|
|
177
|
+
readonly retryable: false;
|
|
178
|
+
}, {
|
|
179
|
+
readonly code: "webhook_payload_not_json";
|
|
180
|
+
readonly type: "authentication_error";
|
|
181
|
+
readonly description: "Webhook signature verified but the body isn't valid JSON — payload tampered post-signing or source bug.";
|
|
182
|
+
readonly resolution: "Inspect the raw payload. If it's not JSON, either the request was modified in transit or the sender has a bug — file a support ticket with the raw body.";
|
|
183
|
+
readonly retryable: false;
|
|
184
|
+
}, {
|
|
185
|
+
readonly code: "webhook_invalid_tolerance";
|
|
186
|
+
readonly type: "configuration_error";
|
|
187
|
+
readonly description: "verifyWebhookSignature() called with a non-finite / negative / above-24h-cap replayToleranceMs (would silently disable replay protection).";
|
|
188
|
+
readonly resolution: "Pass a finite number between 0 and 86_400_000ms (24h). Default (5 minutes) is correct for almost every scenario. Pre-v1.4.0 accepted Infinity/NaN and silently dropped the check.";
|
|
171
189
|
readonly retryable: false;
|
|
172
190
|
}, {
|
|
173
191
|
readonly code: "webhook_missing_secret";
|
|
@@ -175,6 +193,84 @@ declare const _CROSSDECK_ERROR_CODES: readonly [{
|
|
|
175
193
|
readonly description: "verifyWebhookSignature() was called without a signing secret.";
|
|
176
194
|
readonly resolution: "Pass the secret from your Crossdeck dashboard → Webhooks page. Never hardcode in source — read from an env var.";
|
|
177
195
|
readonly retryable: false;
|
|
196
|
+
}, {
|
|
197
|
+
readonly code: "webhook_invalid_signature";
|
|
198
|
+
readonly type: "authentication_error";
|
|
199
|
+
readonly description: "DEPRECATED in v1.4.0 — split into webhook_signature_mismatch / webhook_timestamp_missing / webhook_timestamp_outside_tolerance / webhook_payload_not_json for alerting clarity.";
|
|
200
|
+
readonly resolution: "Migrate alert rules to the more specific v1.4.0 codes — they distinguish replay-attack signals from wrong-secret signals.";
|
|
201
|
+
readonly retryable: false;
|
|
202
|
+
}, {
|
|
203
|
+
readonly code: "webhook_replay_window_exceeded";
|
|
204
|
+
readonly type: "authentication_error";
|
|
205
|
+
readonly description: "DEPRECATED in v1.4.0 — renamed to webhook_timestamp_outside_tolerance.";
|
|
206
|
+
readonly resolution: "Update alerts to webhook_timestamp_outside_tolerance.";
|
|
207
|
+
readonly retryable: false;
|
|
208
|
+
}, {
|
|
209
|
+
readonly code: "missing_api_key";
|
|
210
|
+
readonly type: "authentication_error";
|
|
211
|
+
readonly description: "No Authorization header (or Crossdeck-Api-Key header) on the request.";
|
|
212
|
+
readonly resolution: "Confirm the CrossdeckServer was constructed with a cd_sk_… secretKey. Re-check env vars in production deployments.";
|
|
213
|
+
readonly retryable: false;
|
|
214
|
+
}, {
|
|
215
|
+
readonly code: "invalid_api_key";
|
|
216
|
+
readonly type: "authentication_error";
|
|
217
|
+
readonly description: "The secret key is malformed, unknown, or doesn't resolve to a project.";
|
|
218
|
+
readonly resolution: "Copy the key from Crossdeck dashboard → API keys. Server SDK requires cd_sk_test_ / cd_sk_live_ — client SDK keys (cd_pub_…) won't work on the Node SDK.";
|
|
219
|
+
readonly retryable: false;
|
|
220
|
+
}, {
|
|
221
|
+
readonly code: "key_revoked";
|
|
222
|
+
readonly type: "authentication_error";
|
|
223
|
+
readonly description: "The secret key was revoked in the dashboard.";
|
|
224
|
+
readonly resolution: "Mint a fresh key in dashboard → API keys → Create new. The revoked key cannot be reactivated.";
|
|
225
|
+
readonly retryable: false;
|
|
226
|
+
}, {
|
|
227
|
+
readonly code: "env_mismatch";
|
|
228
|
+
readonly type: "permission_error";
|
|
229
|
+
readonly description: "The key's env prefix doesn't match the resolved app's configured env.";
|
|
230
|
+
readonly resolution: "Use a cd_sk_live_ key with a production app, cd_sk_test_ with a sandbox app. Crossing breaks the env lock.";
|
|
231
|
+
readonly retryable: false;
|
|
232
|
+
}, {
|
|
233
|
+
readonly code: "idempotency_key_in_use";
|
|
234
|
+
readonly type: "invalid_request_error";
|
|
235
|
+
readonly description: "An Idempotency-Key was reused for a request with a different body (Stripe-grade contract).";
|
|
236
|
+
readonly resolution: "Server SDK derives keys deterministically from the body since v1.4.0; this should only fire if you passed options.idempotencyKey explicitly. Use a fresh key per logical operation.";
|
|
237
|
+
readonly retryable: false;
|
|
238
|
+
}, {
|
|
239
|
+
readonly code: "rate_limited";
|
|
240
|
+
readonly type: "rate_limit_error";
|
|
241
|
+
readonly description: "Request rate exceeded the project's per-second cap.";
|
|
242
|
+
readonly resolution: "Honour Retry-After (managed retries do this automatically). For custom paths, throttle to <100 req/s/key.";
|
|
243
|
+
readonly retryable: true;
|
|
244
|
+
}, {
|
|
245
|
+
readonly code: "internal_error";
|
|
246
|
+
readonly type: "internal_error";
|
|
247
|
+
readonly description: "Server-side issue. Safe to retry with backoff.";
|
|
248
|
+
readonly resolution: "Managed retries handle this automatically. If a code path surfaces it to your code, contact support with the requestId.";
|
|
249
|
+
readonly retryable: true;
|
|
250
|
+
}, {
|
|
251
|
+
readonly code: "google_not_supported";
|
|
252
|
+
readonly type: "invalid_request_error";
|
|
253
|
+
readonly description: "POST /purchases/sync with rail=google is gated until the Play Developer API reconciliation worker ships.";
|
|
254
|
+
readonly resolution: "Until v1.5+, Google Play purchases verify via Real-time Developer Notifications. The Android SDK auto-track path handles this transparently.";
|
|
255
|
+
readonly retryable: false;
|
|
256
|
+
}, {
|
|
257
|
+
readonly code: "stripe_not_supported";
|
|
258
|
+
readonly type: "invalid_request_error";
|
|
259
|
+
readonly description: "POST /purchases/sync with rail=stripe is unsupported — Stripe webhooks deliver evidence server-side.";
|
|
260
|
+
readonly resolution: "Wire Stripe via the standard Checkout / Customer Portal flow; Crossdeck reconciles via the platform webhook automatically.";
|
|
261
|
+
readonly retryable: false;
|
|
262
|
+
}, {
|
|
263
|
+
readonly code: "missing_required_param";
|
|
264
|
+
readonly type: "invalid_request_error";
|
|
265
|
+
readonly description: "A required field is absent from the request body.";
|
|
266
|
+
readonly resolution: "The error.message identifies the missing field. Refer to the SDK's TypeScript types for canonical shapes.";
|
|
267
|
+
readonly retryable: false;
|
|
268
|
+
}, {
|
|
269
|
+
readonly code: "invalid_param_value";
|
|
270
|
+
readonly type: "invalid_request_error";
|
|
271
|
+
readonly description: "A field is present but the value failed validation.";
|
|
272
|
+
readonly resolution: "Read error.message for the field + reason. SDK-managed call sites should never emit this — file a bug if you do.";
|
|
273
|
+
readonly retryable: false;
|
|
178
274
|
}];
|
|
179
275
|
/**
|
|
180
276
|
* Literal union of every code documented in `CROSSDECK_ERROR_CODES`.
|
|
@@ -201,9 +297,22 @@ declare function getErrorCode(code: string): ErrorCodeEntry | undefined;
|
|
|
201
297
|
/**
|
|
202
298
|
* Webhook signature verification — Stripe pattern.
|
|
203
299
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
300
|
+
* **[ROADMAP — v1.4.0 honesty note]:** Crossdeck does NOT yet send
|
|
301
|
+
* outbound webhooks. Outbound delivery (signer + worker + scheduler
|
|
302
|
+
* + dead-letter dashboard) is on the post-v1.5 roadmap. This
|
|
303
|
+
* verifier exists today so customer-side integration code can be
|
|
304
|
+
* written and tested against fixtures (use `signWebhookPayload`
|
|
305
|
+
* to produce signed bodies for your local tests), and so the
|
|
306
|
+
* verification contract surface is locked in BEFORE delivery
|
|
307
|
+
* ships — Phase 7.2 of the bank-grade reconciliation tightened
|
|
308
|
+
* the timestamp-validation footguns here precisely because the
|
|
309
|
+
* helper IS the contract surface for inbound validation,
|
|
310
|
+
* regardless of when first-party delivery lights up.
|
|
311
|
+
*
|
|
312
|
+
* Lets customers verify the events Crossdeck sends to THEM (when
|
|
313
|
+
* delivery ships). Table-stakes for any backend SDK (Stripe ships
|
|
314
|
+
* `Stripe.webhooks.constructEvent()` from day one, Svix ships
|
|
315
|
+
* `Webhook.verify()` from day one).
|
|
207
316
|
*
|
|
208
317
|
* Wire format:
|
|
209
318
|
* Header `Crossdeck-Signature: t=<unix-seconds>,v1=<hex>`
|
|
@@ -244,9 +353,21 @@ declare function getErrorCode(code: string): ErrorCodeEntry | undefined;
|
|
|
244
353
|
interface VerifyWebhookOptions {
|
|
245
354
|
/**
|
|
246
355
|
* Maximum age of the webhook timestamp in milliseconds. Default
|
|
247
|
-
* 5 minutes (
|
|
248
|
-
* as a replay.
|
|
249
|
-
*
|
|
356
|
+
* 5 minutes (`DEFAULT_REPLAY_TOLERANCE_MS`). Anything older than
|
|
357
|
+
* this is rejected as a replay.
|
|
358
|
+
*
|
|
359
|
+
* **v1.4.0 Phase 7.2 bank-grade contract:** the timestamp window
|
|
360
|
+
* is MANDATORY. Pre-v1.4.0 the helper accepted `tolerance: 0`
|
|
361
|
+
* (silently disables the check) and `tolerance: Infinity` /
|
|
362
|
+
* `null` / `NaN` (silently disables via `Math.abs(...) > Infinity
|
|
363
|
+
* = false`). Customers relying on replay protection silently
|
|
364
|
+
* lost it.
|
|
365
|
+
*
|
|
366
|
+
* The helper now rejects non-finite / negative / above-cap
|
|
367
|
+
* tolerances at the boundary with a typed
|
|
368
|
+
* `webhook_invalid_tolerance` error. Hard upper bound is 24h —
|
|
369
|
+
* sufficient for any plausible clock-skew scenario, prevents
|
|
370
|
+
* "Infinity by typo" from defeating replay protection.
|
|
250
371
|
*/
|
|
251
372
|
replayToleranceMs?: number;
|
|
252
373
|
/**
|
|
@@ -257,13 +378,21 @@ interface VerifyWebhookOptions {
|
|
|
257
378
|
}
|
|
258
379
|
/**
|
|
259
380
|
* Verify a Crossdeck-signed webhook. Returns the parsed JSON payload
|
|
260
|
-
* on success. Throws `CrossdeckError`
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* -
|
|
265
|
-
* -
|
|
266
|
-
*
|
|
381
|
+
* on success. Throws `CrossdeckError` with one of these
|
|
382
|
+
* distinguishable codes (v1.4.0 Phase 7.2 — pre-v1.4.0 conflated
|
|
383
|
+
* everything under `webhook_invalid_signature`; alerting can now
|
|
384
|
+
* separate replay-attack signals from wrong-secret signals):
|
|
385
|
+
* - `webhook_missing_secret` — no secret configured.
|
|
386
|
+
* - `webhook_invalid_tolerance` — caller passed Infinity / NaN /
|
|
387
|
+
* negative / above-24h-cap `replayToleranceMs`.
|
|
388
|
+
* - `webhook_timestamp_missing` — header absent or has no `t=`.
|
|
389
|
+
* - `webhook_timestamp_outside_tolerance` — drift > tolerance
|
|
390
|
+
* (replay-attack signal — split this from signature mismatch
|
|
391
|
+
* in your alerting rules).
|
|
392
|
+
* - `webhook_signature_mismatch` — HMAC didn't match any
|
|
393
|
+
* configured secret (wrong-secret / rotation-drift signal).
|
|
394
|
+
* - `webhook_payload_not_json` — signature verified but the body
|
|
395
|
+
* isn't parseable JSON (tampered post-signing or source bug).
|
|
267
396
|
*
|
|
268
397
|
* `secret` accepts a single string or an array of strings (for
|
|
269
398
|
* rotation). Any one match is sufficient.
|