@selfxyz/enterprise-sdk 0.1.0 → 0.2.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 +116 -13
- package/dist/index.d.ts +79 -23
- package/dist/index.js +139 -60
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @selfxyz/enterprise-sdk
|
|
2
2
|
|
|
3
|
-
Official Node/TypeScript SDK for the [Self.xyz](https://self.xyz) Enterprise API
|
|
3
|
+
Official Node/TypeScript SDK for the [Self.xyz](https://self.xyz) Enterprise API. Create verification sessions and verify webhooks with typed payloads.
|
|
4
4
|
|
|
5
5
|
> **Pre-1.0 stability**: this SDK is under active development. Minor versions may include breaking changes until `1.0.0`. Pin to an exact version in production.
|
|
6
6
|
|
|
@@ -30,7 +30,7 @@ const self = new SelfClient({ apiKey: process.env.SELF_API_KEY! });
|
|
|
30
30
|
|
|
31
31
|
const session = await self.sessions.create({
|
|
32
32
|
flowId: '<your-flow-uuid>',
|
|
33
|
-
externalUuid: 'user-
|
|
33
|
+
externalUuid: '<user-uuid>',
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
// Redirect the end user here:
|
|
@@ -54,7 +54,7 @@ import { SelfWebhooks } from '@selfxyz/enterprise-sdk';
|
|
|
54
54
|
app.post('/webhooks/self', (req, res) => {
|
|
55
55
|
try {
|
|
56
56
|
const event = SelfWebhooks.verify(
|
|
57
|
-
req.body, // raw string or Buffer
|
|
57
|
+
req.body, // raw string or Buffer (do NOT pre-parse)
|
|
58
58
|
req.headers, // must include the signature headers as received
|
|
59
59
|
process.env.SELF_WEBHOOK_SECRET!, // whsec_... from the Self dashboard
|
|
60
60
|
);
|
|
@@ -67,32 +67,135 @@ app.post('/webhooks/self', (req, res) => {
|
|
|
67
67
|
});
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
The raw request body must be passed exactly as received
|
|
70
|
+
The raw request body must be passed exactly as received. Middleware that JSON-parses the body before signature verification will cause it to fail.
|
|
71
|
+
|
|
72
|
+
## Proof re-verification
|
|
73
|
+
|
|
74
|
+
`verifyProof` lets you independently re-verify a proof you received from Self.
|
|
75
|
+
|
|
76
|
+
Persist `event.proof`, `event.proof_attributes`, and `event.environment` from the `verification.completed` payload — those three fields are everything `verifyProof` needs to re-run offline whenever you want.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { verifyProof } from '@selfxyz/enterprise-sdk';
|
|
80
|
+
|
|
81
|
+
// `verificationData` is whatever you stored when the
|
|
82
|
+
// `verification.completed` webhook arrived — keep `event.proof`,
|
|
83
|
+
// `event.proof_attributes`, and `event.environment` so this call
|
|
84
|
+
// can run offline at any time.
|
|
85
|
+
const { isValid } = await verifyProof({
|
|
86
|
+
orgId: process.env.SELF_ORG_ID!,
|
|
87
|
+
proof: verificationData.proof,
|
|
88
|
+
proofAttributes: verificationData.proof_attributes,
|
|
89
|
+
environment: verificationData.environment,
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`isValid` is true only when the Groth16 proof verifies, the minimum-age predicate is satisfied (if configured), and the user is not on an OFAC list (if OFAC checking is configured).
|
|
94
|
+
|
|
95
|
+
### `orgId`
|
|
96
|
+
|
|
97
|
+
`verifyProof` requires your organization UUID — it's needed to verify the proof. You can find it on the dashboard under any product's **Deploy** tab → **Re-verify proofs** step.
|
|
71
98
|
|
|
72
99
|
## Error handling
|
|
73
100
|
|
|
74
|
-
The SDK throws
|
|
101
|
+
The SDK throws three distinct error types:
|
|
102
|
+
|
|
103
|
+
- **`SelfValidationError`**: bad arguments. Raised before any network call when an SDK input (e.g. `flowId`) fails the schema check, or by `SelfWebhooks.verify` when a webhook payload doesn't match a known event shape.
|
|
104
|
+
- **`SelfApiError`**: non-2xx response from the API.
|
|
105
|
+
- **`WebhookVerificationError`**: invalid or missing webhook signature (re-exported from `svix`).
|
|
106
|
+
|
|
107
|
+
Network-level failures (DNS errors, connection refused, request aborts) propagate as the underlying `TypeError`/`AbortError` from `fetch`. They are **not** wrapped in `SelfApiError`. Handle them separately if you need to distinguish network problems from API responses.
|
|
75
108
|
|
|
76
109
|
```ts
|
|
77
|
-
import {
|
|
110
|
+
import {
|
|
111
|
+
SelfApiError,
|
|
112
|
+
SelfValidationError,
|
|
113
|
+
WebhookVerificationError,
|
|
114
|
+
} from '@selfxyz/enterprise-sdk';
|
|
78
115
|
|
|
79
116
|
try {
|
|
80
117
|
await self.sessions.create({ flowId, externalUuid });
|
|
81
118
|
} catch (err) {
|
|
82
|
-
if (err instanceof
|
|
83
|
-
err.
|
|
84
|
-
err.
|
|
85
|
-
|
|
86
|
-
err.
|
|
119
|
+
if (err instanceof SelfValidationError) {
|
|
120
|
+
err.message; // 'Invalid sessions.create input: flowId (Invalid uuid)'
|
|
121
|
+
err.issues; // ZodIssue[] (raw issues for programmatic handling)
|
|
122
|
+
} else if (err instanceof SelfApiError) {
|
|
123
|
+
err.statusCode; // number (HTTP status)
|
|
124
|
+
err.code; // string (machine-readable error code; see table below)
|
|
125
|
+
err.message; // string (human-readable)
|
|
126
|
+
err.details; // Record<string, unknown> | undefined (see "When details is populated")
|
|
127
|
+
err.requestId; // string | undefined (X-Request-Id from the response)
|
|
87
128
|
}
|
|
88
129
|
throw err;
|
|
89
130
|
}
|
|
90
131
|
```
|
|
91
132
|
|
|
92
|
-
|
|
133
|
+
### Error codes
|
|
134
|
+
|
|
135
|
+
`SelfApiError.code` carries a machine-readable error code so consumers can branch on the failure mode:
|
|
136
|
+
|
|
137
|
+
| Code | HTTP status | Meaning |
|
|
138
|
+
| -------------------- | ----------- | ---------------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| `validation_failed` | 400 | Request body failed server-side validation. |
|
|
140
|
+
| `unauthenticated` | 401 | Missing, invalid, or revoked API key. |
|
|
141
|
+
| `unauthenticated` | 402 | Insufficient credits / billing gate triggered. Disambiguate from 401 via `statusCode` and check `details`. |
|
|
142
|
+
| `forbidden` | 403 | API key lacks permission for the resource. |
|
|
143
|
+
| `not_found` | 404 | Flow or session not found. |
|
|
144
|
+
| `conflict` | 409 | Flow has no published version, or other state conflict. |
|
|
145
|
+
| `rate_limited` | 429 | Rate limit exceeded. Back off and retry. |
|
|
146
|
+
| `internal_error` | 500 | Unhandled server error. |
|
|
147
|
+
| `vendor_unavailable` | 503 | Upstream dependency is degraded (e.g. circuit breaker open). |
|
|
148
|
+
| `unknown_error` | any | API response didn't match the expected envelope. The `message` field includes a body preview. |
|
|
149
|
+
|
|
150
|
+
The set of codes may grow over time. Always include a default branch in any `switch (err.code)` so future codes don't fall through silently.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
try {
|
|
154
|
+
await self.sessions.create({ flowId, externalUuid });
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (!(err instanceof SelfApiError)) throw err;
|
|
157
|
+
switch (err.code) {
|
|
158
|
+
case 'rate_limited':
|
|
159
|
+
// back off and retry
|
|
160
|
+
break;
|
|
161
|
+
case 'unauthenticated':
|
|
162
|
+
if (err.statusCode === 402) {
|
|
163
|
+
// billing gate. err.details is { balance, required, planTier, creditGateMode }
|
|
164
|
+
} else {
|
|
165
|
+
// bad API key (401)
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case 'not_found':
|
|
169
|
+
// flow or session doesn't exist
|
|
170
|
+
break;
|
|
171
|
+
default:
|
|
172
|
+
// unknown_error or any future code. Log err.requestId and err.message
|
|
173
|
+
console.error('Self API error', { code: err.code, requestId: err.requestId });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### When `details` is populated
|
|
179
|
+
|
|
180
|
+
`SelfApiError.details` is the server's optional context object. It's set on the following errors:
|
|
181
|
+
|
|
182
|
+
**`unauthenticated` + `statusCode: 402` (billing gate)**: emitted when your organization's credit balance is below what the requested verification costs:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
{
|
|
186
|
+
balance: number; // spendable credits at request time
|
|
187
|
+
required: number; // credits needed for this request
|
|
188
|
+
planTier: 'free' | 'starter' | 'enterprise';
|
|
189
|
+
creditGateMode: 'hard'; // always 'hard' (soft-gate orgs do not see this error)
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**`validation_failed` + `statusCode: 400`**: `{ issues: ZodIssue[] }`. The SDK pre-validates request bodies and throws `SelfValidationError` before sending, so this server-side variant is normally caught client-side. It can surface if the SDK and server schemas fall out of sync; pin an exact SDK version in production to avoid this.
|
|
194
|
+
|
|
195
|
+
For every other code, `details` is `undefined`.
|
|
93
196
|
|
|
94
197
|
## License
|
|
95
198
|
|
|
96
|
-
Licensed under the [Business Source License 1.1](
|
|
199
|
+
Licensed under the [Business Source License 1.1](https://github.com/selfxyz/self/blob/main/app/LICENSE). © 2025 Socialconnect Labs, Inc.
|
|
97
200
|
|
|
98
201
|
On **2029-06-11** the license converts automatically to the **Apache License, Version 2.0**.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
1
|
+
import { z, ZodIssue, ZodError } from 'zod';
|
|
2
2
|
export { WebhookVerificationError } from 'svix';
|
|
3
3
|
|
|
4
4
|
declare const verificationCompleted: z.ZodObject<{
|
|
@@ -46,21 +46,21 @@ declare const verificationStorageCommitted: z.ZodObject<{
|
|
|
46
46
|
type: z.ZodLiteral<"verification.storage_committed">;
|
|
47
47
|
verification_id: z.ZodString;
|
|
48
48
|
external_uuid: z.ZodString;
|
|
49
|
-
storage_uri: z.ZodString
|
|
49
|
+
storage_uri: z.ZodNullable<z.ZodString>;
|
|
50
50
|
credential_id: z.ZodString;
|
|
51
51
|
committed_at: z.ZodString;
|
|
52
52
|
}, "strip", z.ZodTypeAny, {
|
|
53
53
|
type: "verification.storage_committed";
|
|
54
54
|
verification_id: string;
|
|
55
55
|
external_uuid: string;
|
|
56
|
-
storage_uri: string;
|
|
56
|
+
storage_uri: string | null;
|
|
57
57
|
credential_id: string;
|
|
58
58
|
committed_at: string;
|
|
59
59
|
}, {
|
|
60
60
|
type: "verification.storage_committed";
|
|
61
61
|
verification_id: string;
|
|
62
62
|
external_uuid: string;
|
|
63
|
-
storage_uri: string;
|
|
63
|
+
storage_uri: string | null;
|
|
64
64
|
credential_id: string;
|
|
65
65
|
committed_at: string;
|
|
66
66
|
}>;
|
|
@@ -72,15 +72,15 @@ declare const verificationStorageFailed: z.ZodObject<{
|
|
|
72
72
|
failed_at: z.ZodString;
|
|
73
73
|
}, "strip", z.ZodTypeAny, {
|
|
74
74
|
type: "verification.storage_failed";
|
|
75
|
-
error: string;
|
|
76
75
|
verification_id: string;
|
|
77
76
|
external_uuid: string;
|
|
77
|
+
error: string;
|
|
78
78
|
failed_at: string;
|
|
79
79
|
}, {
|
|
80
80
|
type: "verification.storage_failed";
|
|
81
|
-
error: string;
|
|
82
81
|
verification_id: string;
|
|
83
82
|
external_uuid: string;
|
|
83
|
+
error: string;
|
|
84
84
|
failed_at: string;
|
|
85
85
|
}>;
|
|
86
86
|
declare const webhookEvent: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
@@ -127,21 +127,21 @@ declare const webhookEvent: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
|
127
127
|
type: z.ZodLiteral<"verification.storage_committed">;
|
|
128
128
|
verification_id: z.ZodString;
|
|
129
129
|
external_uuid: z.ZodString;
|
|
130
|
-
storage_uri: z.ZodString
|
|
130
|
+
storage_uri: z.ZodNullable<z.ZodString>;
|
|
131
131
|
credential_id: z.ZodString;
|
|
132
132
|
committed_at: z.ZodString;
|
|
133
133
|
}, "strip", z.ZodTypeAny, {
|
|
134
134
|
type: "verification.storage_committed";
|
|
135
135
|
verification_id: string;
|
|
136
136
|
external_uuid: string;
|
|
137
|
-
storage_uri: string;
|
|
137
|
+
storage_uri: string | null;
|
|
138
138
|
credential_id: string;
|
|
139
139
|
committed_at: string;
|
|
140
140
|
}, {
|
|
141
141
|
type: "verification.storage_committed";
|
|
142
142
|
verification_id: string;
|
|
143
143
|
external_uuid: string;
|
|
144
|
-
storage_uri: string;
|
|
144
|
+
storage_uri: string | null;
|
|
145
145
|
credential_id: string;
|
|
146
146
|
committed_at: string;
|
|
147
147
|
}>, z.ZodObject<{
|
|
@@ -152,15 +152,15 @@ declare const webhookEvent: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
|
152
152
|
failed_at: z.ZodString;
|
|
153
153
|
}, "strip", z.ZodTypeAny, {
|
|
154
154
|
type: "verification.storage_failed";
|
|
155
|
-
error: string;
|
|
156
155
|
verification_id: string;
|
|
157
156
|
external_uuid: string;
|
|
157
|
+
error: string;
|
|
158
158
|
failed_at: string;
|
|
159
159
|
}, {
|
|
160
160
|
type: "verification.storage_failed";
|
|
161
|
-
error: string;
|
|
162
161
|
verification_id: string;
|
|
163
162
|
external_uuid: string;
|
|
163
|
+
error: string;
|
|
164
164
|
failed_at: string;
|
|
165
165
|
}>]>;
|
|
166
166
|
type VerificationCompletedPayload = z.infer<typeof verificationCompleted>;
|
|
@@ -185,8 +185,8 @@ declare const createSessionBody: z.ZodObject<{
|
|
|
185
185
|
}, {
|
|
186
186
|
flowId: string;
|
|
187
187
|
externalUuid: string;
|
|
188
|
-
expiresInSeconds?: number | undefined;
|
|
189
188
|
metadata?: Record<string, unknown> | undefined;
|
|
189
|
+
expiresInSeconds?: number | undefined;
|
|
190
190
|
successUrl?: string | undefined;
|
|
191
191
|
failureUrl?: string | undefined;
|
|
192
192
|
}>;
|
|
@@ -254,9 +254,9 @@ declare const sessionDetailResponse: z.ZodObject<{
|
|
|
254
254
|
}>;
|
|
255
255
|
}, "strip", z.ZodTypeAny, {
|
|
256
256
|
id: string;
|
|
257
|
-
status: "valid" | "
|
|
258
|
-
externalUuid: string;
|
|
257
|
+
status: "valid" | "invalid" | "error" | "expired" | "pending";
|
|
259
258
|
metadata: Record<string, unknown> | null;
|
|
259
|
+
externalUuid: string;
|
|
260
260
|
flowVersionId: string;
|
|
261
261
|
createdAt: string;
|
|
262
262
|
expiresAt: string;
|
|
@@ -270,9 +270,9 @@ declare const sessionDetailResponse: z.ZodObject<{
|
|
|
270
270
|
};
|
|
271
271
|
}, {
|
|
272
272
|
id: string;
|
|
273
|
-
status: "valid" | "
|
|
274
|
-
externalUuid: string;
|
|
273
|
+
status: "valid" | "invalid" | "error" | "expired" | "pending";
|
|
275
274
|
metadata: Record<string, unknown> | null;
|
|
275
|
+
externalUuid: string;
|
|
276
276
|
flowVersionId: string;
|
|
277
277
|
createdAt: string;
|
|
278
278
|
expiresAt: string;
|
|
@@ -291,9 +291,7 @@ type SessionDetailResponse = z.infer<typeof sessionDetailResponse>;
|
|
|
291
291
|
interface SelfClientOptions {
|
|
292
292
|
/** Bearer API key (e.g. `sk_live_...`). */
|
|
293
293
|
apiKey: string;
|
|
294
|
-
/**
|
|
295
|
-
environment?: 'test' | 'live';
|
|
296
|
-
/** Override the base URL (default: `https://api.self.xyz`). */
|
|
294
|
+
/** Override the base URL (default: `https://edge.dashboard.self.xyz`). */
|
|
297
295
|
baseUrl?: string;
|
|
298
296
|
}
|
|
299
297
|
/**
|
|
@@ -303,10 +301,14 @@ interface SelfClientOptions {
|
|
|
303
301
|
* const self = new SelfClient({ apiKey: 'sk_live_...' });
|
|
304
302
|
* const session = await self.sessions.create({
|
|
305
303
|
* flowId: '<uuid>',
|
|
306
|
-
* externalUuid: '
|
|
304
|
+
* externalUuid: '<uuid>',
|
|
307
305
|
* });
|
|
308
306
|
* console.log(session.verificationUrl);
|
|
309
307
|
* ```
|
|
308
|
+
*
|
|
309
|
+
* Methods throw `SelfValidationError` for bad arguments and `SelfApiError`
|
|
310
|
+
* for non-2xx API responses. See the package README for the full error-code
|
|
311
|
+
* table.
|
|
310
312
|
*/
|
|
311
313
|
declare class SelfClient {
|
|
312
314
|
private readonly apiKey;
|
|
@@ -349,7 +351,7 @@ declare class SelfWebhooks {
|
|
|
349
351
|
* @param secret Webhook signing secret (`whsec_...` from the Self dashboard).
|
|
350
352
|
* @returns Parsed and verified {@link WebhookEvent}.
|
|
351
353
|
* @throws `WebhookVerificationError` if the signature is invalid.
|
|
352
|
-
* @throws `
|
|
354
|
+
* @throws `SelfValidationError` if the payload shape doesn't match any known event type.
|
|
353
355
|
*/
|
|
354
356
|
static verify(payload: string | Buffer, headers: WebhookHeaders | Record<string, string>, secret: string): WebhookEvent;
|
|
355
357
|
}
|
|
@@ -359,15 +361,69 @@ interface ApiErrorBody {
|
|
|
359
361
|
message: string;
|
|
360
362
|
details?: Record<string, unknown>;
|
|
361
363
|
}
|
|
364
|
+
interface SelfApiErrorOptions {
|
|
365
|
+
/** Value of the `X-Request-Id` response header, if present. */
|
|
366
|
+
requestId?: string;
|
|
367
|
+
}
|
|
362
368
|
/**
|
|
363
369
|
* Thrown when the Self API returns a non-2xx response.
|
|
364
370
|
* Wraps the standard `{ error: { code, message, details? } }` envelope.
|
|
365
371
|
*/
|
|
366
372
|
declare class SelfApiError extends Error {
|
|
373
|
+
readonly name: "SelfApiError";
|
|
367
374
|
readonly statusCode: number;
|
|
368
375
|
readonly code: string;
|
|
369
376
|
readonly details: Record<string, unknown> | undefined;
|
|
370
|
-
|
|
377
|
+
/** `X-Request-Id` from the response. Include when reporting issues to Self support. */
|
|
378
|
+
readonly requestId: string | undefined;
|
|
379
|
+
constructor(statusCode: number, body: ApiErrorBody, options?: SelfApiErrorOptions);
|
|
371
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Thrown when arguments passed to the SDK (session inputs, webhook payloads)
|
|
383
|
+
* fail schema validation before the request is sent / after a webhook is
|
|
384
|
+
* verified. Wraps a `ZodError` with a human-readable message.
|
|
385
|
+
*/
|
|
386
|
+
declare class SelfValidationError extends Error {
|
|
387
|
+
readonly name: "SelfValidationError";
|
|
388
|
+
readonly issues: ZodIssue[];
|
|
389
|
+
constructor(zodError: ZodError, context: string);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
interface VerifiableProofEnvelope {
|
|
393
|
+
attestationId: number;
|
|
394
|
+
proof: {
|
|
395
|
+
a: [string, string];
|
|
396
|
+
b: [[string, string], [string, string]];
|
|
397
|
+
c: [string, string];
|
|
398
|
+
curve: string;
|
|
399
|
+
protocol: 'groth16';
|
|
400
|
+
};
|
|
401
|
+
publicSignals: string[];
|
|
402
|
+
userContextData: string;
|
|
403
|
+
}
|
|
404
|
+
interface VerifyProofParams {
|
|
405
|
+
/** Your organization UUID — required to verify the proof. Find it on the
|
|
406
|
+
* dashboard under any product's Deploy tab → Re-verify proofs step. */
|
|
407
|
+
orgId: string;
|
|
408
|
+
/** Raw proof envelope as received in `verification.completed.proof`. */
|
|
409
|
+
proof: VerifiableProofEnvelope;
|
|
410
|
+
/** Predicates the proof was checked against (from `verification.completed.proof_attributes`). */
|
|
411
|
+
proofAttributes: Record<string, unknown>;
|
|
412
|
+
/** From `verification.completed.environment`. Picks the right verifier endpoint constant. */
|
|
413
|
+
environment: 'test' | 'live';
|
|
414
|
+
}
|
|
415
|
+
interface VerifyProofResult {
|
|
416
|
+
isValid: boolean;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Independently re-verify a proof you received from Self.
|
|
420
|
+
*
|
|
421
|
+
* `isValid` is true iff the Groth16 proof verifies, the minimum-age
|
|
422
|
+
* predicate is satisfied (if configured), and the user is not on an OFAC
|
|
423
|
+
* list (if OFAC checking is configured).
|
|
424
|
+
*
|
|
425
|
+
* Throws an `Error` if `orgId` is missing.
|
|
426
|
+
*/
|
|
427
|
+
declare function verifyProof(params: VerifyProofParams): Promise<VerifyProofResult>;
|
|
372
428
|
|
|
373
|
-
export { type ApiErrorBody, type CreateSessionBody, type CreateSessionInput, SelfApiError, SelfClient, type SelfClientOptions, SelfWebhooks, type CreateSessionResponse as Session, type SessionDetailResponse as SessionDetail, type VerificationCompletedPayload, type VerificationStorageCommittedPayload, type VerificationStorageFailedPayload, type WebhookEvent, type WebhookHeaders, createSessionBody, createSessionResponse, sessionDetailResponse, verificationCompleted, verificationStorageCommitted, verificationStorageFailed, webhookEvent };
|
|
429
|
+
export { type ApiErrorBody, type CreateSessionBody, type CreateSessionInput, SelfApiError, SelfClient, type SelfClientOptions, SelfValidationError, SelfWebhooks, type CreateSessionResponse as Session, type SessionDetailResponse as SessionDetail, type VerifiableProofEnvelope, type VerificationCompletedPayload, type VerificationStorageCommittedPayload, type VerificationStorageFailedPayload, type VerifyProofParams, type VerifyProofResult, type WebhookEvent, type WebhookHeaders, createSessionBody, createSessionResponse, sessionDetailResponse, verificationCompleted, verificationStorageCommitted, verificationStorageFailed, verifyProof, webhookEvent };
|
package/dist/index.js
CHANGED
|
@@ -1,73 +1,39 @@
|
|
|
1
1
|
// src/errors.ts
|
|
2
2
|
import { WebhookVerificationError } from "svix";
|
|
3
3
|
var SelfApiError = class extends Error {
|
|
4
|
+
name = "SelfApiError";
|
|
4
5
|
statusCode;
|
|
5
6
|
code;
|
|
6
7
|
details;
|
|
7
|
-
|
|
8
|
+
/** `X-Request-Id` from the response. Include when reporting issues to Self support. */
|
|
9
|
+
requestId;
|
|
10
|
+
constructor(statusCode, body, options) {
|
|
8
11
|
super(body.message);
|
|
9
|
-
this.name = "SelfApiError";
|
|
10
12
|
this.statusCode = statusCode;
|
|
11
13
|
this.code = body.code;
|
|
12
14
|
this.details = body.details;
|
|
15
|
+
this.requestId = options?.requestId;
|
|
13
16
|
}
|
|
14
17
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
constructor(options) {
|
|
22
|
-
if (!options.apiKey) {
|
|
23
|
-
throw new Error("apiKey is required");
|
|
24
|
-
}
|
|
25
|
-
this.apiKey = options.apiKey;
|
|
26
|
-
this.baseUrl = (options.baseUrl ?? "https://api.self.xyz").replace(/\/+$/, "");
|
|
27
|
-
this.sessions = {
|
|
28
|
-
create: (params) => this.createSession(params),
|
|
29
|
-
get: (id) => this.getSession(id)
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
async request(method, path, body) {
|
|
33
|
-
const url = `${this.baseUrl}${path}`;
|
|
34
|
-
const res = await fetch(url, {
|
|
35
|
-
method,
|
|
36
|
-
headers: {
|
|
37
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
38
|
-
"Content-Type": "application/json",
|
|
39
|
-
Accept: "application/json"
|
|
40
|
-
},
|
|
41
|
-
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
42
|
-
});
|
|
43
|
-
const text = await res.text();
|
|
44
|
-
let json;
|
|
45
|
-
try {
|
|
46
|
-
json = JSON.parse(text);
|
|
47
|
-
} catch {
|
|
48
|
-
json = void 0;
|
|
49
|
-
}
|
|
50
|
-
if (!res.ok) {
|
|
51
|
-
const envelope = json;
|
|
52
|
-
throw new SelfApiError(res.status, {
|
|
53
|
-
code: envelope?.error?.code ?? "unknown_error",
|
|
54
|
-
message: envelope?.error?.message ?? (text || res.statusText),
|
|
55
|
-
...envelope?.error?.details ? { details: envelope.error.details } : {}
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
return json;
|
|
59
|
-
}
|
|
60
|
-
createSession(params) {
|
|
61
|
-
return this.request("POST", "/v1/sessions", params);
|
|
62
|
-
}
|
|
63
|
-
getSession(id) {
|
|
64
|
-
return this.request("GET", `/v1/sessions/${encodeURIComponent(id)}`);
|
|
18
|
+
var SelfValidationError = class extends Error {
|
|
19
|
+
name = "SelfValidationError";
|
|
20
|
+
issues;
|
|
21
|
+
constructor(zodError, context) {
|
|
22
|
+
super(formatZodIssues(zodError.issues, context));
|
|
23
|
+
this.issues = zodError.issues;
|
|
65
24
|
}
|
|
66
25
|
};
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
26
|
+
function formatZodIssues(issues, context) {
|
|
27
|
+
if (issues.length === 0) {
|
|
28
|
+
return `Invalid ${context}`;
|
|
29
|
+
}
|
|
30
|
+
const formatted = issues.map(formatIssue).join("; ");
|
|
31
|
+
return `Invalid ${context}: ${formatted}`;
|
|
32
|
+
}
|
|
33
|
+
function formatIssue(issue) {
|
|
34
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
35
|
+
return `${path} (${issue.message})`;
|
|
36
|
+
}
|
|
71
37
|
|
|
72
38
|
// ../schemas/dist/stripe-events.js
|
|
73
39
|
import { z } from "zod";
|
|
@@ -237,7 +203,7 @@ var verificationStorageCommitted = z3.object({
|
|
|
237
203
|
type: z3.literal("verification.storage_committed"),
|
|
238
204
|
verification_id: z3.string(),
|
|
239
205
|
external_uuid: z3.string(),
|
|
240
|
-
storage_uri: z3.string(),
|
|
206
|
+
storage_uri: z3.string().nullable(),
|
|
241
207
|
credential_id: z3.string(),
|
|
242
208
|
committed_at: z3.string().datetime()
|
|
243
209
|
});
|
|
@@ -258,7 +224,7 @@ var webhookEvent = z3.discriminatedUnion("type", [
|
|
|
258
224
|
import { z as z4 } from "zod";
|
|
259
225
|
var createSessionBody = z4.object({
|
|
260
226
|
flowId: z4.string().uuid(),
|
|
261
|
-
externalUuid: z4.string().
|
|
227
|
+
externalUuid: z4.string().uuid(),
|
|
262
228
|
expiresInSeconds: z4.number().int().min(60).max(86400).default(3600),
|
|
263
229
|
metadata: z4.record(z4.unknown()).optional().refine((val) => !val || JSON.stringify(val).length <= 4096, {
|
|
264
230
|
message: "metadata must be 4KB or less when serialized"
|
|
@@ -300,7 +266,79 @@ var sessionDetailResponse = z5.object({
|
|
|
300
266
|
})
|
|
301
267
|
});
|
|
302
268
|
|
|
269
|
+
// src/client.ts
|
|
270
|
+
var SelfClient = class {
|
|
271
|
+
apiKey;
|
|
272
|
+
baseUrl;
|
|
273
|
+
sessions;
|
|
274
|
+
constructor(options) {
|
|
275
|
+
if (!options.apiKey) {
|
|
276
|
+
throw new Error("apiKey is required");
|
|
277
|
+
}
|
|
278
|
+
this.apiKey = options.apiKey;
|
|
279
|
+
this.baseUrl = (options.baseUrl ?? "https://edge.dashboard.self.xyz").replace(/\/+$/, "");
|
|
280
|
+
this.sessions = {
|
|
281
|
+
create: (params) => this.createSession(params),
|
|
282
|
+
get: (id) => this.getSession(id)
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async request(method, path, body) {
|
|
286
|
+
const url = `${this.baseUrl}${path}`;
|
|
287
|
+
const res = await fetch(url, {
|
|
288
|
+
method,
|
|
289
|
+
headers: {
|
|
290
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
Accept: "application/json"
|
|
293
|
+
},
|
|
294
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
295
|
+
});
|
|
296
|
+
const text = await res.text();
|
|
297
|
+
let json;
|
|
298
|
+
try {
|
|
299
|
+
json = JSON.parse(text);
|
|
300
|
+
} catch {
|
|
301
|
+
json = void 0;
|
|
302
|
+
}
|
|
303
|
+
if (!res.ok) {
|
|
304
|
+
const envelope = json;
|
|
305
|
+
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
306
|
+
throw new SelfApiError(
|
|
307
|
+
res.status,
|
|
308
|
+
{
|
|
309
|
+
code: envelope?.error?.code ?? "unknown_error",
|
|
310
|
+
message: envelope?.error?.message ?? fallbackMessage(text, res.statusText, json !== void 0),
|
|
311
|
+
...envelope?.error?.details ? { details: envelope.error.details } : {}
|
|
312
|
+
},
|
|
313
|
+
requestId !== void 0 ? { requestId } : void 0
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return json;
|
|
317
|
+
}
|
|
318
|
+
async createSession(params) {
|
|
319
|
+
const result = createSessionBody.safeParse(params);
|
|
320
|
+
if (!result.success) {
|
|
321
|
+
throw new SelfValidationError(result.error, "sessions.create input");
|
|
322
|
+
}
|
|
323
|
+
return this.request("POST", "/v1/sessions", result.data);
|
|
324
|
+
}
|
|
325
|
+
getSession(id) {
|
|
326
|
+
return this.request("GET", `/v1/sessions/${encodeURIComponent(id)}`);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
var BODY_PREVIEW_LIMIT = 200;
|
|
330
|
+
function fallbackMessage(text, statusText, jsonParsed) {
|
|
331
|
+
if (text.length === 0) {
|
|
332
|
+
return `${statusText} (empty response body)`;
|
|
333
|
+
}
|
|
334
|
+
const truncated = text.length > BODY_PREVIEW_LIMIT ? `${text.slice(0, BODY_PREVIEW_LIMIT)}\u2026` : text;
|
|
335
|
+
const label = jsonParsed ? "response body" : "non-JSON response body";
|
|
336
|
+
return `${statusText} (${label}: ${JSON.stringify(truncated)})`;
|
|
337
|
+
}
|
|
338
|
+
|
|
303
339
|
// src/webhooks.ts
|
|
340
|
+
import { z as z6 } from "zod";
|
|
341
|
+
import { Webhook } from "svix";
|
|
304
342
|
var forwardCompatibleEvent = z6.discriminatedUnion("type", [
|
|
305
343
|
verificationCompleted.passthrough(),
|
|
306
344
|
verificationStorageCommitted.passthrough(),
|
|
@@ -315,7 +353,7 @@ var SelfWebhooks = class {
|
|
|
315
353
|
* @param secret Webhook signing secret (`whsec_...` from the Self dashboard).
|
|
316
354
|
* @returns Parsed and verified {@link WebhookEvent}.
|
|
317
355
|
* @throws `WebhookVerificationError` if the signature is invalid.
|
|
318
|
-
* @throws `
|
|
356
|
+
* @throws `SelfValidationError` if the payload shape doesn't match any known event type.
|
|
319
357
|
*/
|
|
320
358
|
static verify(payload, headers, secret) {
|
|
321
359
|
const wh = new Webhook(secret);
|
|
@@ -323,12 +361,52 @@ var SelfWebhooks = class {
|
|
|
323
361
|
typeof payload === "string" ? payload : payload.toString("utf-8"),
|
|
324
362
|
headers
|
|
325
363
|
);
|
|
326
|
-
|
|
364
|
+
const result = forwardCompatibleEvent.safeParse(verified);
|
|
365
|
+
if (!result.success) {
|
|
366
|
+
throw new SelfValidationError(result.error, "webhook payload");
|
|
367
|
+
}
|
|
368
|
+
return result.data;
|
|
327
369
|
}
|
|
328
370
|
};
|
|
371
|
+
|
|
372
|
+
// src/verify-proof.ts
|
|
373
|
+
import { AllIds, DefaultConfigStore, SelfBackendVerifier } from "@selfxyz/core";
|
|
374
|
+
var VERIFIER_ENDPOINTS = {
|
|
375
|
+
live: "https://verifier.self.xyz/verify",
|
|
376
|
+
test: "https://verifier.staging.self.xyz/verify"
|
|
377
|
+
};
|
|
378
|
+
function orgIdToScope(orgId) {
|
|
379
|
+
return orgId.replaceAll("-", "").slice(0, 30);
|
|
380
|
+
}
|
|
381
|
+
async function verifyProof(params) {
|
|
382
|
+
if (!params.orgId) {
|
|
383
|
+
throw new Error("verifyProof: orgId is required");
|
|
384
|
+
}
|
|
385
|
+
const verifier = new SelfBackendVerifier(
|
|
386
|
+
orgIdToScope(params.orgId),
|
|
387
|
+
VERIFIER_ENDPOINTS[params.environment],
|
|
388
|
+
params.environment === "test",
|
|
389
|
+
AllIds,
|
|
390
|
+
new DefaultConfigStore(params.proofAttributes),
|
|
391
|
+
"uuid"
|
|
392
|
+
);
|
|
393
|
+
try {
|
|
394
|
+
const result = await verifier.verify(
|
|
395
|
+
params.proof.attestationId,
|
|
396
|
+
params.proof.proof,
|
|
397
|
+
params.proof.publicSignals,
|
|
398
|
+
params.proof.userContextData
|
|
399
|
+
);
|
|
400
|
+
const { isValid, isMinimumAgeValid, isOfacValid } = result.isValidDetails;
|
|
401
|
+
return { isValid: isValid && isMinimumAgeValid && !isOfacValid };
|
|
402
|
+
} catch {
|
|
403
|
+
return { isValid: false };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
329
406
|
export {
|
|
330
407
|
SelfApiError,
|
|
331
408
|
SelfClient,
|
|
409
|
+
SelfValidationError,
|
|
332
410
|
SelfWebhooks,
|
|
333
411
|
WebhookVerificationError,
|
|
334
412
|
createSessionBody,
|
|
@@ -337,5 +415,6 @@ export {
|
|
|
337
415
|
verificationCompleted,
|
|
338
416
|
verificationStorageCommitted,
|
|
339
417
|
verificationStorageFailed,
|
|
418
|
+
verifyProof,
|
|
340
419
|
webhookEvent
|
|
341
420
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selfxyz/enterprise-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Node/TS SDK for Self.xyz enterprise verification
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Node/TS SDK for Self.xyz enterprise verification: session creation, webhook signature verification, typed payloads.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"homepage": "https://self.xyz",
|
|
7
7
|
"repository": {
|
|
@@ -22,9 +22,6 @@
|
|
|
22
22
|
"main": "dist/index.js",
|
|
23
23
|
"types": "dist/index.d.ts",
|
|
24
24
|
"publishConfig": {
|
|
25
|
-
"name": "@selfxyz/enterprise-sdk",
|
|
26
|
-
"main": "dist/index.js",
|
|
27
|
-
"types": "dist/index.d.ts",
|
|
28
25
|
"access": "public"
|
|
29
26
|
},
|
|
30
27
|
"files": [
|
|
@@ -46,6 +43,7 @@
|
|
|
46
43
|
"clean": "rm -rf dist .turbo *.tsbuildinfo"
|
|
47
44
|
},
|
|
48
45
|
"dependencies": {
|
|
46
|
+
"@selfxyz/core": "^1.2.0-beta.1",
|
|
49
47
|
"svix": "^1.92.2",
|
|
50
48
|
"zod": "^3.24.1"
|
|
51
49
|
},
|