@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 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 create verification sessions and verify webhooks with typed payloads.
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-123',
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 do NOT pre-parse
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 middleware that JSON-parses the body before signature verification will cause it to fail.
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 two distinct error types:
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 { SelfApiError, WebhookVerificationError } from '@selfxyz/enterprise-sdk';
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 SelfApiError) {
83
- err.statusCode; // number HTTP status
84
- err.code; // string machine-readable error code
85
- err.message; // string — human-readable
86
- err.details; // Record<string, unknown> | undefined
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
- `WebhookVerificationError` is thrown by `SelfWebhooks.verify` when a signature is invalid or missing.
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](./LICENSE). © 2025 Socialconnect Labs, Inc.
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" | "pending" | "invalid" | "error" | "expired";
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" | "pending" | "invalid" | "error" | "expired";
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
- /** Environment hint. The actual environment is embedded in the API key via Unkey. */
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: 'user-123',
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 `ZodError` if the payload shape doesn't match any known event type.
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
- constructor(statusCode: number, body: ApiErrorBody);
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
- constructor(statusCode, body) {
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
- // src/client.ts
17
- var SelfClient = class {
18
- apiKey;
19
- baseUrl;
20
- sessions;
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
- // src/webhooks.ts
69
- import { z as z6 } from "zod";
70
- import { Webhook } from "svix";
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().min(1).max(128),
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 `ZodError` if the payload shape doesn't match any known event type.
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
- return forwardCompatibleEvent.parse(verified);
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.1.0",
4
- "description": "Node/TS SDK for Self.xyz enterprise verification session creation, webhook signature verification, typed payloads.",
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
  },