vairified 0.7.0 → 0.8.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/README.md +142 -0
- package/dist/index.cjs +227 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +483 -4
- package/dist/index.d.ts +483 -4
- package/dist/index.js +216 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -255,6 +255,148 @@ const result = await client.matches.tournamentImport({
|
|
|
255
255
|
console.log(`Imported ${result.matchesImported} matches, ${result.ghostPlayersCreated} ghosts`);
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
+
## Receiving Webhooks
|
|
259
|
+
|
|
260
|
+
`verifyWebhook()` checks a delivery's signature and hands it back typed. It takes no client
|
|
261
|
+
and no API key — a webhook receiver is an inbound HTTP handler, and often never calls the
|
|
262
|
+
Partner API at all.
|
|
263
|
+
|
|
264
|
+
**Verify on your server, never in a browser or a mobile app.** The signing secret is what
|
|
265
|
+
proves a delivery came from us; anything that ships to a user's device can be read out of it.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { verifyWebhook, isMemberStatusEvent, WebhookSignatureError } from 'vairified';
|
|
269
|
+
|
|
270
|
+
const event = await verifyWebhook(
|
|
271
|
+
rawBody, // the exact bytes — see below
|
|
272
|
+
request.headers['x-vairified-signature'],
|
|
273
|
+
process.env.VAIR_WEBHOOK_SECRET,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
if (isMemberStatusEvent(event)) {
|
|
277
|
+
await entitlements.set(event.data.memberId, {
|
|
278
|
+
vairPlus: event.data.isVairPlus,
|
|
279
|
+
ambassador: event.data.isAmbassador,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
It is `async` — **`await` it.** Verification uses Web Crypto, which has no synchronous HMAC.
|
|
285
|
+
The function never returns a boolean, precisely so that a forgotten `await` cannot be read as
|
|
286
|
+
"the signature was fine".
|
|
287
|
+
|
|
288
|
+
### Capture the raw body
|
|
289
|
+
|
|
290
|
+
The signature covers **the bytes we sent**. A body that has been parsed and re-serialised is
|
|
291
|
+
not those bytes — key order, whitespace and number formatting all move — so every signature
|
|
292
|
+
fails. In Express, that means reaching for `express.raw()` on the webhook route specifically,
|
|
293
|
+
even when the rest of the app uses `express.json()`:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import express from 'express';
|
|
297
|
+
import { verifyWebhook, WebhookSignatureError, dedupeKey } from 'vairified';
|
|
298
|
+
|
|
299
|
+
const app = express();
|
|
300
|
+
|
|
301
|
+
app.post('/webhooks/vairified', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
302
|
+
let event;
|
|
303
|
+
try {
|
|
304
|
+
event = await verifyWebhook(req.body, req.get('X-Vairified-Signature'), [
|
|
305
|
+
process.env.VAIR_WEBHOOK_SECRET,
|
|
306
|
+
process.env.VAIR_WEBHOOK_SECRET_PREVIOUS, // see "Rotating the secret"
|
|
307
|
+
]);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
if (err instanceof WebhookSignatureError) return res.sendStatus(400);
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Respond first, work afterwards — a slow handler is retried as a failure.
|
|
314
|
+
res.sendStatus(200);
|
|
315
|
+
await queue.add(dedupeKey(event), event);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
app.use(express.json()); // everything else
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
In a Fetch-based runtime (Workers, Deno, Bun, Next.js route handlers) `await request.text()`
|
|
322
|
+
already gives you the raw body; pass it straight in. `rawBody` accepts a `string` or a
|
|
323
|
+
`Uint8Array`.
|
|
324
|
+
|
|
325
|
+
### Handling what arrives
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
const event = await verifyWebhook(rawBody, signatureHeader, process.env.VAIR_WEBHOOK_SECRET);
|
|
329
|
+
|
|
330
|
+
if (isMemberStatusEvent(event)) {
|
|
331
|
+
// membership or ambassador standing changed
|
|
332
|
+
} else if (isRatingUpdatedEvent(event)) {
|
|
333
|
+
// a new rating, as a full per-sport snapshot
|
|
334
|
+
} else if (isConnectionRevokedEvent(event)) {
|
|
335
|
+
// the member disconnected your app — stop reading their data
|
|
336
|
+
} else if (isEventCreatedEvent(event)) {
|
|
337
|
+
// a new event was published
|
|
338
|
+
}
|
|
339
|
+
// Anything else is an event type this version does not know about. It still
|
|
340
|
+
// verified, and arrives as the plain envelope — ignore it, or log it.
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
An event type the package does not recognise **verifies successfully and is handed over
|
|
344
|
+
as-is**, and so does an unfamiliar value inside one it does recognise. Both grow on the API's
|
|
345
|
+
schedule rather than this package's, and refusing one would break a working receiver over a
|
|
346
|
+
change that is not a break.
|
|
347
|
+
|
|
348
|
+
### Ordering and duplicates
|
|
349
|
+
|
|
350
|
+
Delivery is at-least-once, and deliveries can arrive out of order.
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
if (isRatingUpdatedEvent(event)) {
|
|
354
|
+
const last = await store.get(event.data.memberId);
|
|
355
|
+
if (last && !isNewerSequence(event.data.sequence, last)) return; // stale — skip it
|
|
356
|
+
await store.put(event.data.memberId, event.data.sequence);
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
- **`sequence` is an unpadded decimal string, so do not compare it as one.** `'10000000' >
|
|
361
|
+
'9999999'` is `false`, which would discard every later delivery for that member from the
|
|
362
|
+
first power-of-ten crossing onward. `isNewerSequence()` and `compareSequence()` compare as
|
|
363
|
+
integers.
|
|
364
|
+
- **Deduplicate with `dedupeKey(event)`**, which returns the id from the *signed body*. The
|
|
365
|
+
`X-Vairified-Event-Id` header carries the same value but is outside the signature, so a
|
|
366
|
+
replayed delivery can present a fresh one and header-based deduplication admits it every
|
|
367
|
+
time.
|
|
368
|
+
|
|
369
|
+
### Rotating the secret
|
|
370
|
+
|
|
371
|
+
`secret` accepts an array, and any one match verifies:
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
const event = await verifyWebhook(rawBody, signatureHeader, [
|
|
375
|
+
process.env.VAIR_WEBHOOK_SECRET,
|
|
376
|
+
process.env.VAIR_WEBHOOK_SECRET_PREVIOUS,
|
|
377
|
+
]);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Deliveries queued before you rotated were signed with the **old** secret and keep arriving for
|
|
381
|
+
hours afterwards. A verifier that knows only the new one discards them silently, so keep the
|
|
382
|
+
previous secret configured until the retry tail has drained.
|
|
383
|
+
|
|
384
|
+
### Why a delivery was refused
|
|
385
|
+
|
|
386
|
+
Every refusal is a `WebhookSignatureError` with a `reason` you can branch on:
|
|
387
|
+
|
|
388
|
+
| `reason` | Means |
|
|
389
|
+
|---|---|
|
|
390
|
+
| `no_secret_configured` | Your configuration, not an attack — almost always an unset environment variable. |
|
|
391
|
+
| `invalid_option` | A tolerance or clock you passed in was not usable. |
|
|
392
|
+
| `missing_signature` | No `X-Vairified-Signature` header. |
|
|
393
|
+
| `malformed_signature` | The header was present but unparseable, or had no `t` / `v1`. |
|
|
394
|
+
| `timestamp_out_of_tolerance` | The delivery's clock and yours disagree by more than the window (5 minutes by default, applied in both directions). |
|
|
395
|
+
| `signature_mismatch` | The body does not match the signature under any secret supplied. |
|
|
396
|
+
| `malformed_body` | The body was not JSON, or a field a partner gates access on was missing or the wrong type. |
|
|
397
|
+
|
|
398
|
+
The message never contains the secret or either digest.
|
|
399
|
+
|
|
258
400
|
## Webhook Deliveries
|
|
259
401
|
|
|
260
402
|
Inspect recent webhook delivery attempts for your app:
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AuthenticationError: () => AuthenticationError,
|
|
24
24
|
DEFAULT_SCOPES: () => DEFAULT_SCOPES,
|
|
25
|
+
DEFAULT_TOLERANCE_SECONDS: () => DEFAULT_TOLERANCE_SECONDS,
|
|
25
26
|
ENVIRONMENTS: () => ENVIRONMENTS,
|
|
26
27
|
LeaderboardResource: () => LeaderboardResource,
|
|
27
28
|
MatchBatchResult: () => MatchBatchResult,
|
|
@@ -44,12 +45,21 @@ __export(index_exports, {
|
|
|
44
45
|
ValidationError: () => ValidationError,
|
|
45
46
|
WebhookDeliveriesResult: () => WebhookDeliveriesResult,
|
|
46
47
|
WebhookDelivery: () => WebhookDelivery,
|
|
48
|
+
WebhookSignatureError: () => WebhookSignatureError,
|
|
47
49
|
WebhooksResource: () => WebhooksResource,
|
|
50
|
+
compareSequence: () => compareSequence,
|
|
51
|
+
dedupeKey: () => dedupeKey,
|
|
48
52
|
describeScope: () => describeScope,
|
|
49
53
|
describeScopes: () => describeScopes,
|
|
50
54
|
generateState: () => generateState,
|
|
51
55
|
getAuthorizationUrl: () => getAuthorizationUrl,
|
|
52
|
-
|
|
56
|
+
isConnectionRevokedEvent: () => isConnectionRevokedEvent,
|
|
57
|
+
isEventCreatedEvent: () => isEventCreatedEvent,
|
|
58
|
+
isMemberStatusEvent: () => isMemberStatusEvent,
|
|
59
|
+
isNewerSequence: () => isNewerSequence,
|
|
60
|
+
isRatingUpdatedEvent: () => isRatingUpdatedEvent,
|
|
61
|
+
validateScope: () => validateScope,
|
|
62
|
+
verifyWebhook: () => verifyWebhook
|
|
53
63
|
});
|
|
54
64
|
module.exports = __toCommonJS(index_exports);
|
|
55
65
|
|
|
@@ -105,6 +115,14 @@ var OAuthError = class extends VairifiedError {
|
|
|
105
115
|
this.errorCode = errorCode;
|
|
106
116
|
}
|
|
107
117
|
};
|
|
118
|
+
var WebhookSignatureError = class extends VairifiedError {
|
|
119
|
+
reason;
|
|
120
|
+
constructor(reason, message) {
|
|
121
|
+
super(message);
|
|
122
|
+
this.name = "WebhookSignatureError";
|
|
123
|
+
this.reason = reason;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
108
126
|
|
|
109
127
|
// src/http.ts
|
|
110
128
|
var HttpTransport = class {
|
|
@@ -1575,10 +1593,208 @@ var Vairified = class {
|
|
|
1575
1593
|
return `Vairified { env: '${this.env}', baseUrl: '${this.baseUrl}' }`;
|
|
1576
1594
|
}
|
|
1577
1595
|
};
|
|
1596
|
+
|
|
1597
|
+
// src/webhooks/verify.ts
|
|
1598
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
1599
|
+
var SIGNATURE_HEADER = "X-Vairified-Signature";
|
|
1600
|
+
var encoder = new TextEncoder();
|
|
1601
|
+
function toBytes(body) {
|
|
1602
|
+
return typeof body === "string" ? encoder.encode(body) : body;
|
|
1603
|
+
}
|
|
1604
|
+
function parseSignatureHeader(header) {
|
|
1605
|
+
let timestamp = null;
|
|
1606
|
+
let rawTimestamp = null;
|
|
1607
|
+
let signature = null;
|
|
1608
|
+
for (const part of header.split(",")) {
|
|
1609
|
+
const eq = part.indexOf("=");
|
|
1610
|
+
if (eq === -1) continue;
|
|
1611
|
+
const key = part.slice(0, eq).trim();
|
|
1612
|
+
const value = part.slice(eq + 1).trim();
|
|
1613
|
+
if (key === "t" && timestamp === null) {
|
|
1614
|
+
if (!/^\d+$/.test(value)) {
|
|
1615
|
+
throw new WebhookSignatureError(
|
|
1616
|
+
"malformed_signature",
|
|
1617
|
+
`${SIGNATURE_HEADER} carried a timestamp that is not an integer`
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
timestamp = Number(value);
|
|
1621
|
+
rawTimestamp = value;
|
|
1622
|
+
} else if (key === "v1" && signature === null) {
|
|
1623
|
+
signature = value;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
if (timestamp === null || rawTimestamp === null || signature === null) {
|
|
1627
|
+
throw new WebhookSignatureError(
|
|
1628
|
+
"malformed_signature",
|
|
1629
|
+
`${SIGNATURE_HEADER} must carry both a 't' and a 'v1' part`
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
return { timestamp, rawTimestamp, signature };
|
|
1633
|
+
}
|
|
1634
|
+
var MAX_SIGNATURE_HEX = 64;
|
|
1635
|
+
function hexToBytes(hex) {
|
|
1636
|
+
if (hex.length > MAX_SIGNATURE_HEX) {
|
|
1637
|
+
throw new WebhookSignatureError(
|
|
1638
|
+
"malformed_signature",
|
|
1639
|
+
`${SIGNATURE_HEADER} carried a 'v1' value longer than a SHA-256 digest`
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
if (hex.length === 0 || hex.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hex)) {
|
|
1643
|
+
throw new WebhookSignatureError(
|
|
1644
|
+
"malformed_signature",
|
|
1645
|
+
`${SIGNATURE_HEADER} carried a 'v1' value that is not a hex digest`
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
const out = new Uint8Array(hex.length / 2);
|
|
1649
|
+
for (let i = 0; i < out.length; i++) {
|
|
1650
|
+
out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
1651
|
+
}
|
|
1652
|
+
return out;
|
|
1653
|
+
}
|
|
1654
|
+
function assertEnvelope(value) {
|
|
1655
|
+
if (typeof value !== "object" || value === null) {
|
|
1656
|
+
throw new WebhookSignatureError("malformed_body", "The webhook body is not a JSON object");
|
|
1657
|
+
}
|
|
1658
|
+
const o = value;
|
|
1659
|
+
for (const field of ["event", "eventId", "timestamp"]) {
|
|
1660
|
+
if (typeof o[field] !== "string") {
|
|
1661
|
+
throw new WebhookSignatureError(
|
|
1662
|
+
"malformed_body",
|
|
1663
|
+
`The webhook body is missing a string '${field}'`
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
function assertKnownEventShape(event) {
|
|
1669
|
+
const bad = (field) => {
|
|
1670
|
+
throw new WebhookSignatureError(
|
|
1671
|
+
"malformed_body",
|
|
1672
|
+
`A '${event.event}' event is missing a valid '${field}'`
|
|
1673
|
+
);
|
|
1674
|
+
};
|
|
1675
|
+
const data = event.data;
|
|
1676
|
+
if (typeof data !== "object" || data === null) {
|
|
1677
|
+
if (event.event === "member.status" || event.event === "rating.updated") bad("data");
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
if (event.event === "member.status") {
|
|
1681
|
+
if (typeof data.memberId !== "number") bad("data.memberId");
|
|
1682
|
+
if (typeof data.isVairPlus !== "boolean") bad("data.isVairPlus");
|
|
1683
|
+
if (typeof data.isAmbassador !== "boolean") bad("data.isAmbassador");
|
|
1684
|
+
} else if (event.event === "rating.updated") {
|
|
1685
|
+
if (typeof data.memberId !== "number") bad("data.memberId");
|
|
1686
|
+
if (typeof data.sequence !== "string") bad("data.sequence");
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
async function verifyWebhook(rawBody, signatureHeader, secret, options = {}) {
|
|
1690
|
+
const candidates = typeof secret === "string" ? [secret] : Array.isArray(secret) ? secret : [];
|
|
1691
|
+
const secrets = candidates.filter(
|
|
1692
|
+
(s) => typeof s === "string" && s.trim().length > 0
|
|
1693
|
+
);
|
|
1694
|
+
if (secrets.length === 0) {
|
|
1695
|
+
throw new WebhookSignatureError(
|
|
1696
|
+
"no_secret_configured",
|
|
1697
|
+
"No usable signing secret was supplied"
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
if (!signatureHeader) {
|
|
1701
|
+
throw new WebhookSignatureError(
|
|
1702
|
+
"missing_signature",
|
|
1703
|
+
`No ${SIGNATURE_HEADER} header was supplied`
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
1707
|
+
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
1708
|
+
if (!Number.isFinite(tolerance) || tolerance < 0) {
|
|
1709
|
+
throw new WebhookSignatureError(
|
|
1710
|
+
"invalid_option",
|
|
1711
|
+
"toleranceSeconds must be a finite, non-negative number"
|
|
1712
|
+
);
|
|
1713
|
+
}
|
|
1714
|
+
if (!Number.isFinite(now)) {
|
|
1715
|
+
throw new WebhookSignatureError("invalid_option", "nowSeconds must be a finite number");
|
|
1716
|
+
}
|
|
1717
|
+
const { timestamp, rawTimestamp, signature } = parseSignatureHeader(signatureHeader);
|
|
1718
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
1719
|
+
throw new WebhookSignatureError(
|
|
1720
|
+
"timestamp_out_of_tolerance",
|
|
1721
|
+
`The delivery timestamp is outside the ${tolerance}s tolerance`
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
const signatureBytes = hexToBytes(signature);
|
|
1725
|
+
const bodyBytes = toBytes(rawBody);
|
|
1726
|
+
const prefix = encoder.encode(`${rawTimestamp}.`);
|
|
1727
|
+
const signed = new Uint8Array(prefix.length + bodyBytes.length);
|
|
1728
|
+
signed.set(prefix, 0);
|
|
1729
|
+
signed.set(bodyBytes, prefix.length);
|
|
1730
|
+
let matched = false;
|
|
1731
|
+
for (const candidate of secrets) {
|
|
1732
|
+
const key = await crypto.subtle.importKey(
|
|
1733
|
+
"raw",
|
|
1734
|
+
encoder.encode(candidate),
|
|
1735
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
1736
|
+
false,
|
|
1737
|
+
["verify"]
|
|
1738
|
+
);
|
|
1739
|
+
matched = await crypto.subtle.verify("HMAC", key, signatureBytes, signed) || matched;
|
|
1740
|
+
}
|
|
1741
|
+
if (!matched) {
|
|
1742
|
+
throw new WebhookSignatureError(
|
|
1743
|
+
"signature_mismatch",
|
|
1744
|
+
"The webhook signature did not match any supplied secret"
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
let parsed;
|
|
1748
|
+
try {
|
|
1749
|
+
parsed = JSON.parse(
|
|
1750
|
+
new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
|
|
1751
|
+
signed.subarray(prefix.length)
|
|
1752
|
+
)
|
|
1753
|
+
);
|
|
1754
|
+
} catch {
|
|
1755
|
+
throw new WebhookSignatureError("malformed_body", "The webhook body is not valid JSON");
|
|
1756
|
+
}
|
|
1757
|
+
assertEnvelope(parsed);
|
|
1758
|
+
assertKnownEventShape(parsed);
|
|
1759
|
+
return deepFreeze(parsed);
|
|
1760
|
+
}
|
|
1761
|
+
function deepFreeze(value) {
|
|
1762
|
+
if (value === null || typeof value !== "object" || Object.isFrozen(value)) return value;
|
|
1763
|
+
Object.freeze(value);
|
|
1764
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
1765
|
+
deepFreeze(value[key]);
|
|
1766
|
+
}
|
|
1767
|
+
return value;
|
|
1768
|
+
}
|
|
1769
|
+
function isMemberStatusEvent(event) {
|
|
1770
|
+
return event.event === "member.status";
|
|
1771
|
+
}
|
|
1772
|
+
function isConnectionRevokedEvent(event) {
|
|
1773
|
+
return event.event === "connection.revoked";
|
|
1774
|
+
}
|
|
1775
|
+
function isRatingUpdatedEvent(event) {
|
|
1776
|
+
return event.event === "rating.updated";
|
|
1777
|
+
}
|
|
1778
|
+
function isEventCreatedEvent(event) {
|
|
1779
|
+
return event.event === "event.created";
|
|
1780
|
+
}
|
|
1781
|
+
function compareSequence(a, b) {
|
|
1782
|
+
const x = BigInt(a);
|
|
1783
|
+
const y = BigInt(b);
|
|
1784
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
1785
|
+
}
|
|
1786
|
+
function isNewerSequence(incoming, lastApplied) {
|
|
1787
|
+
if (lastApplied === void 0 || lastApplied === null || lastApplied === "") return true;
|
|
1788
|
+
return compareSequence(incoming, lastApplied) > 0;
|
|
1789
|
+
}
|
|
1790
|
+
function dedupeKey(event) {
|
|
1791
|
+
return event.eventId;
|
|
1792
|
+
}
|
|
1578
1793
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1579
1794
|
0 && (module.exports = {
|
|
1580
1795
|
AuthenticationError,
|
|
1581
1796
|
DEFAULT_SCOPES,
|
|
1797
|
+
DEFAULT_TOLERANCE_SECONDS,
|
|
1582
1798
|
ENVIRONMENTS,
|
|
1583
1799
|
LeaderboardResource,
|
|
1584
1800
|
MatchBatchResult,
|
|
@@ -1601,11 +1817,20 @@ var Vairified = class {
|
|
|
1601
1817
|
ValidationError,
|
|
1602
1818
|
WebhookDeliveriesResult,
|
|
1603
1819
|
WebhookDelivery,
|
|
1820
|
+
WebhookSignatureError,
|
|
1604
1821
|
WebhooksResource,
|
|
1822
|
+
compareSequence,
|
|
1823
|
+
dedupeKey,
|
|
1605
1824
|
describeScope,
|
|
1606
1825
|
describeScopes,
|
|
1607
1826
|
generateState,
|
|
1608
1827
|
getAuthorizationUrl,
|
|
1609
|
-
|
|
1828
|
+
isConnectionRevokedEvent,
|
|
1829
|
+
isEventCreatedEvent,
|
|
1830
|
+
isMemberStatusEvent,
|
|
1831
|
+
isNewerSequence,
|
|
1832
|
+
isRatingUpdatedEvent,
|
|
1833
|
+
validateScope,
|
|
1834
|
+
verifyWebhook
|
|
1610
1835
|
});
|
|
1611
1836
|
//# sourceMappingURL=index.cjs.map
|