@selfxyz/enterprise-sdk 0.1.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/LICENSE ADDED
@@ -0,0 +1,35 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: Social Connect Labs, Inc.
6
+
7
+ Licensed Work: The code in this folder and all folders nested within it, including all files and components unless explicitly stated otherwise.
8
+
9
+ Additional Use Grant: Any use for development, testing, and deployment on networks approved by Social Connect Labs, Inc., including internal business operations and academic research.
10
+
11
+ Change Date: 2029-06-11
12
+
13
+ Change License: Apache License, Version 2.0
14
+
15
+ ================================================================
16
+
17
+ Business Source License 1.1
18
+
19
+ Copyright (C) 2025 Social Connect Labs, Inc.
20
+
21
+ The contents of this repository are licensed under the Business Source License 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:
22
+
23
+ https://spdx.org/licenses/BUSL-1.1.html
24
+
25
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26
+
27
+ Change Date
28
+
29
+ The Change Date is the later of four years from the date this repository was published or 2029-06-11.
30
+
31
+ Change License
32
+
33
+ On the Change Date, the contents of this repository will be made available under the terms of the Apache License, Version 2.0, as published by the Apache Software Foundation.
34
+
35
+ See the License for the specific language governing permissions and limitations under the Business Source License.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @selfxyz/enterprise-sdk
2
+
3
+ Official Node/TypeScript SDK for the [Self.xyz](https://self.xyz) Enterprise API — create verification sessions and verify webhooks with typed payloads.
4
+
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
+
7
+ ## Requirements
8
+
9
+ - Node.js **≥ 20**
10
+ - ESM-only (the package is `"type": "module"`; use it from ESM consumers, or via dynamic `import()` from CommonJS)
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pnpm add @selfxyz/enterprise-sdk
16
+ # or
17
+ npm install @selfxyz/enterprise-sdk
18
+ # or
19
+ yarn add @selfxyz/enterprise-sdk
20
+ ```
21
+
22
+ ## Quickstart
23
+
24
+ Create a verification session and hand the returned URL to your end user:
25
+
26
+ ```ts
27
+ import { SelfClient } from '@selfxyz/enterprise-sdk';
28
+
29
+ const self = new SelfClient({ apiKey: process.env.SELF_API_KEY! });
30
+
31
+ const session = await self.sessions.create({
32
+ flowId: '<your-flow-uuid>',
33
+ externalUuid: 'user-123',
34
+ });
35
+
36
+ // Redirect the end user here:
37
+ console.log(session.verificationUrl);
38
+ ```
39
+
40
+ To look up a session later:
41
+
42
+ ```ts
43
+ const detail = await self.sessions.get(session.id);
44
+ console.log(detail.status); // 'pending' | 'valid' | 'invalid' | 'error' | 'expired'
45
+ ```
46
+
47
+ ## Webhook verification
48
+
49
+ Use `SelfWebhooks.verify` to validate an inbound webhook signature and return a typed event:
50
+
51
+ ```ts
52
+ import { SelfWebhooks } from '@selfxyz/enterprise-sdk';
53
+
54
+ app.post('/webhooks/self', (req, res) => {
55
+ try {
56
+ const event = SelfWebhooks.verify(
57
+ req.body, // raw string or Buffer — do NOT pre-parse
58
+ req.headers, // must include the signature headers as received
59
+ process.env.SELF_WEBHOOK_SECRET!, // whsec_... from the Self dashboard
60
+ );
61
+
62
+ console.log(event.type, event.verification_id);
63
+ res.status(200).end();
64
+ } catch (err) {
65
+ res.status(400).end();
66
+ }
67
+ });
68
+ ```
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.
71
+
72
+ ## Error handling
73
+
74
+ The SDK throws two distinct error types:
75
+
76
+ ```ts
77
+ import { SelfApiError, WebhookVerificationError } from '@selfxyz/enterprise-sdk';
78
+
79
+ try {
80
+ await self.sessions.create({ flowId, externalUuid });
81
+ } 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
87
+ }
88
+ throw err;
89
+ }
90
+ ```
91
+
92
+ `WebhookVerificationError` is thrown by `SelfWebhooks.verify` when a signature is invalid or missing.
93
+
94
+ ## License
95
+
96
+ Licensed under the [Business Source License 1.1](./LICENSE). © 2025 Socialconnect Labs, Inc.
97
+
98
+ On **2029-06-11** the license converts automatically to the **Apache License, Version 2.0**.
@@ -0,0 +1,373 @@
1
+ import { z } from 'zod';
2
+ export { WebhookVerificationError } from 'svix';
3
+
4
+ declare const verificationCompleted: z.ZodObject<{
5
+ type: z.ZodLiteral<"verification.completed">;
6
+ verification_id: z.ZodString;
7
+ external_uuid: z.ZodString;
8
+ flow_id: z.ZodString;
9
+ flow_version_id: z.ZodString;
10
+ environment: z.ZodEnum<["test", "live"]>;
11
+ status: z.ZodEnum<["valid", "invalid", "error", "expired"]>;
12
+ proof_attributes: z.ZodRecord<z.ZodString, z.ZodUnknown>;
13
+ /** Raw ZK proof JSON. Present when status is 'valid'; null otherwise. */
14
+ proof: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
15
+ verified_at: z.ZodString;
16
+ storage_state: z.ZodEnum<["pending", "committed", "failed"]>;
17
+ storage_uri: z.ZodNullable<z.ZodString>;
18
+ }, "strip", z.ZodTypeAny, {
19
+ type: "verification.completed";
20
+ status: "valid" | "invalid" | "error" | "expired";
21
+ verification_id: string;
22
+ external_uuid: string;
23
+ flow_id: string;
24
+ flow_version_id: string;
25
+ environment: "test" | "live";
26
+ proof_attributes: Record<string, unknown>;
27
+ proof: Record<string, unknown> | null;
28
+ verified_at: string;
29
+ storage_state: "pending" | "committed" | "failed";
30
+ storage_uri: string | null;
31
+ }, {
32
+ type: "verification.completed";
33
+ status: "valid" | "invalid" | "error" | "expired";
34
+ verification_id: string;
35
+ external_uuid: string;
36
+ flow_id: string;
37
+ flow_version_id: string;
38
+ environment: "test" | "live";
39
+ proof_attributes: Record<string, unknown>;
40
+ proof: Record<string, unknown> | null;
41
+ verified_at: string;
42
+ storage_state: "pending" | "committed" | "failed";
43
+ storage_uri: string | null;
44
+ }>;
45
+ declare const verificationStorageCommitted: z.ZodObject<{
46
+ type: z.ZodLiteral<"verification.storage_committed">;
47
+ verification_id: z.ZodString;
48
+ external_uuid: z.ZodString;
49
+ storage_uri: z.ZodString;
50
+ credential_id: z.ZodString;
51
+ committed_at: z.ZodString;
52
+ }, "strip", z.ZodTypeAny, {
53
+ type: "verification.storage_committed";
54
+ verification_id: string;
55
+ external_uuid: string;
56
+ storage_uri: string;
57
+ credential_id: string;
58
+ committed_at: string;
59
+ }, {
60
+ type: "verification.storage_committed";
61
+ verification_id: string;
62
+ external_uuid: string;
63
+ storage_uri: string;
64
+ credential_id: string;
65
+ committed_at: string;
66
+ }>;
67
+ declare const verificationStorageFailed: z.ZodObject<{
68
+ type: z.ZodLiteral<"verification.storage_failed">;
69
+ verification_id: z.ZodString;
70
+ external_uuid: z.ZodString;
71
+ error: z.ZodString;
72
+ failed_at: z.ZodString;
73
+ }, "strip", z.ZodTypeAny, {
74
+ type: "verification.storage_failed";
75
+ error: string;
76
+ verification_id: string;
77
+ external_uuid: string;
78
+ failed_at: string;
79
+ }, {
80
+ type: "verification.storage_failed";
81
+ error: string;
82
+ verification_id: string;
83
+ external_uuid: string;
84
+ failed_at: string;
85
+ }>;
86
+ declare const webhookEvent: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
87
+ type: z.ZodLiteral<"verification.completed">;
88
+ verification_id: z.ZodString;
89
+ external_uuid: z.ZodString;
90
+ flow_id: z.ZodString;
91
+ flow_version_id: z.ZodString;
92
+ environment: z.ZodEnum<["test", "live"]>;
93
+ status: z.ZodEnum<["valid", "invalid", "error", "expired"]>;
94
+ proof_attributes: z.ZodRecord<z.ZodString, z.ZodUnknown>;
95
+ /** Raw ZK proof JSON. Present when status is 'valid'; null otherwise. */
96
+ proof: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
97
+ verified_at: z.ZodString;
98
+ storage_state: z.ZodEnum<["pending", "committed", "failed"]>;
99
+ storage_uri: z.ZodNullable<z.ZodString>;
100
+ }, "strip", z.ZodTypeAny, {
101
+ type: "verification.completed";
102
+ status: "valid" | "invalid" | "error" | "expired";
103
+ verification_id: string;
104
+ external_uuid: string;
105
+ flow_id: string;
106
+ flow_version_id: string;
107
+ environment: "test" | "live";
108
+ proof_attributes: Record<string, unknown>;
109
+ proof: Record<string, unknown> | null;
110
+ verified_at: string;
111
+ storage_state: "pending" | "committed" | "failed";
112
+ storage_uri: string | null;
113
+ }, {
114
+ type: "verification.completed";
115
+ status: "valid" | "invalid" | "error" | "expired";
116
+ verification_id: string;
117
+ external_uuid: string;
118
+ flow_id: string;
119
+ flow_version_id: string;
120
+ environment: "test" | "live";
121
+ proof_attributes: Record<string, unknown>;
122
+ proof: Record<string, unknown> | null;
123
+ verified_at: string;
124
+ storage_state: "pending" | "committed" | "failed";
125
+ storage_uri: string | null;
126
+ }>, z.ZodObject<{
127
+ type: z.ZodLiteral<"verification.storage_committed">;
128
+ verification_id: z.ZodString;
129
+ external_uuid: z.ZodString;
130
+ storage_uri: z.ZodString;
131
+ credential_id: z.ZodString;
132
+ committed_at: z.ZodString;
133
+ }, "strip", z.ZodTypeAny, {
134
+ type: "verification.storage_committed";
135
+ verification_id: string;
136
+ external_uuid: string;
137
+ storage_uri: string;
138
+ credential_id: string;
139
+ committed_at: string;
140
+ }, {
141
+ type: "verification.storage_committed";
142
+ verification_id: string;
143
+ external_uuid: string;
144
+ storage_uri: string;
145
+ credential_id: string;
146
+ committed_at: string;
147
+ }>, z.ZodObject<{
148
+ type: z.ZodLiteral<"verification.storage_failed">;
149
+ verification_id: z.ZodString;
150
+ external_uuid: z.ZodString;
151
+ error: z.ZodString;
152
+ failed_at: z.ZodString;
153
+ }, "strip", z.ZodTypeAny, {
154
+ type: "verification.storage_failed";
155
+ error: string;
156
+ verification_id: string;
157
+ external_uuid: string;
158
+ failed_at: string;
159
+ }, {
160
+ type: "verification.storage_failed";
161
+ error: string;
162
+ verification_id: string;
163
+ external_uuid: string;
164
+ failed_at: string;
165
+ }>]>;
166
+ type VerificationCompletedPayload = z.infer<typeof verificationCompleted>;
167
+ type VerificationStorageCommittedPayload = z.infer<typeof verificationStorageCommitted>;
168
+ type VerificationStorageFailedPayload = z.infer<typeof verificationStorageFailed>;
169
+ type WebhookEvent = z.infer<typeof webhookEvent>;
170
+
171
+ declare const createSessionBody: z.ZodObject<{
172
+ flowId: z.ZodString;
173
+ externalUuid: z.ZodString;
174
+ expiresInSeconds: z.ZodDefault<z.ZodNumber>;
175
+ metadata: z.ZodEffects<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>, Record<string, unknown> | undefined, Record<string, unknown> | undefined>;
176
+ successUrl: z.ZodOptional<z.ZodString>;
177
+ failureUrl: z.ZodOptional<z.ZodString>;
178
+ }, "strip", z.ZodTypeAny, {
179
+ flowId: string;
180
+ externalUuid: string;
181
+ expiresInSeconds: number;
182
+ metadata?: Record<string, unknown> | undefined;
183
+ successUrl?: string | undefined;
184
+ failureUrl?: string | undefined;
185
+ }, {
186
+ flowId: string;
187
+ externalUuid: string;
188
+ expiresInSeconds?: number | undefined;
189
+ metadata?: Record<string, unknown> | undefined;
190
+ successUrl?: string | undefined;
191
+ failureUrl?: string | undefined;
192
+ }>;
193
+ /** Output type (after Zod defaults are applied — expiresInSeconds is required). */
194
+ type CreateSessionBody = z.infer<typeof createSessionBody>;
195
+ /** Input type (what the SDK consumer passes — expiresInSeconds is optional). */
196
+ type CreateSessionInput = z.input<typeof createSessionBody>;
197
+
198
+ declare const createSessionResponse: z.ZodObject<{
199
+ id: z.ZodString;
200
+ externalUuid: z.ZodString;
201
+ status: z.ZodLiteral<"pending">;
202
+ flowVersionId: z.ZodString;
203
+ /** Single-use token (e.g. `verify_live_<random>`). */
204
+ verificationToken: z.ZodString;
205
+ /** Full URL to redirect the end-user to (e.g. `https://verify.self.xyz/s/<token>`). */
206
+ verificationUrl: z.ZodString;
207
+ createdAt: z.ZodString;
208
+ expiresAt: z.ZodString;
209
+ completedAt: z.ZodNull;
210
+ }, "strip", z.ZodTypeAny, {
211
+ id: string;
212
+ status: "pending";
213
+ externalUuid: string;
214
+ flowVersionId: string;
215
+ verificationToken: string;
216
+ verificationUrl: string;
217
+ createdAt: string;
218
+ expiresAt: string;
219
+ completedAt: null;
220
+ }, {
221
+ id: string;
222
+ status: "pending";
223
+ externalUuid: string;
224
+ flowVersionId: string;
225
+ verificationToken: string;
226
+ verificationUrl: string;
227
+ createdAt: string;
228
+ expiresAt: string;
229
+ completedAt: null;
230
+ }>;
231
+ declare const sessionDetailResponse: z.ZodObject<{
232
+ id: z.ZodString;
233
+ status: z.ZodEnum<["pending", "valid", "invalid", "error", "expired"]>;
234
+ createdAt: z.ZodString;
235
+ completedAt: z.ZodNullable<z.ZodString>;
236
+ expiresAt: z.ZodString;
237
+ flowVersionId: z.ZodString;
238
+ externalUuid: z.ZodString;
239
+ metadata: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
240
+ predicatesConfig: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
241
+ proofAttributes: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
242
+ storage: z.ZodObject<{
243
+ state: z.ZodEnum<["pending", "committed", "failed"]>;
244
+ uri: z.ZodNullable<z.ZodString>;
245
+ credentialId: z.ZodNullable<z.ZodString>;
246
+ }, "strip", z.ZodTypeAny, {
247
+ state: "pending" | "committed" | "failed";
248
+ uri: string | null;
249
+ credentialId: string | null;
250
+ }, {
251
+ state: "pending" | "committed" | "failed";
252
+ uri: string | null;
253
+ credentialId: string | null;
254
+ }>;
255
+ }, "strip", z.ZodTypeAny, {
256
+ id: string;
257
+ status: "valid" | "pending" | "invalid" | "error" | "expired";
258
+ externalUuid: string;
259
+ metadata: Record<string, unknown> | null;
260
+ flowVersionId: string;
261
+ createdAt: string;
262
+ expiresAt: string;
263
+ completedAt: string | null;
264
+ predicatesConfig: Record<string, unknown> | null;
265
+ proofAttributes: Record<string, unknown> | null;
266
+ storage: {
267
+ state: "pending" | "committed" | "failed";
268
+ uri: string | null;
269
+ credentialId: string | null;
270
+ };
271
+ }, {
272
+ id: string;
273
+ status: "valid" | "pending" | "invalid" | "error" | "expired";
274
+ externalUuid: string;
275
+ metadata: Record<string, unknown> | null;
276
+ flowVersionId: string;
277
+ createdAt: string;
278
+ expiresAt: string;
279
+ completedAt: string | null;
280
+ predicatesConfig: Record<string, unknown> | null;
281
+ proofAttributes: Record<string, unknown> | null;
282
+ storage: {
283
+ state: "pending" | "committed" | "failed";
284
+ uri: string | null;
285
+ credentialId: string | null;
286
+ };
287
+ }>;
288
+ type CreateSessionResponse = z.infer<typeof createSessionResponse>;
289
+ type SessionDetailResponse = z.infer<typeof sessionDetailResponse>;
290
+
291
+ interface SelfClientOptions {
292
+ /** Bearer API key (e.g. `sk_live_...`). */
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`). */
297
+ baseUrl?: string;
298
+ }
299
+ /**
300
+ * Node/TS client for the Self.xyz Edge API.
301
+ *
302
+ * ```ts
303
+ * const self = new SelfClient({ apiKey: 'sk_live_...' });
304
+ * const session = await self.sessions.create({
305
+ * flowId: '<uuid>',
306
+ * externalUuid: 'user-123',
307
+ * });
308
+ * console.log(session.verificationUrl);
309
+ * ```
310
+ */
311
+ declare class SelfClient {
312
+ private readonly apiKey;
313
+ private readonly baseUrl;
314
+ readonly sessions: {
315
+ create: (params: CreateSessionInput) => Promise<CreateSessionResponse>;
316
+ get: (id: string) => Promise<SessionDetailResponse>;
317
+ };
318
+ constructor(options: SelfClientOptions);
319
+ private request;
320
+ private createSession;
321
+ private getSession;
322
+ }
323
+
324
+ interface WebhookHeaders {
325
+ 'svix-id': string;
326
+ 'svix-timestamp': string;
327
+ 'svix-signature': string;
328
+ }
329
+ /**
330
+ * Verify and parse inbound Self webhook payloads signed by Svix.
331
+ *
332
+ * ```ts
333
+ * app.post('/webhooks/self', (req, res) => {
334
+ * const event = SelfWebhooks.verify(
335
+ * req.body, // raw string body
336
+ * req.headers, // must include svix-id, svix-timestamp, svix-signature
337
+ * process.env.SELF_WEBHOOK_SECRET!, // whsec_... from dashboard
338
+ * );
339
+ * console.log(event.type, event.verification_id);
340
+ * });
341
+ * ```
342
+ */
343
+ declare class SelfWebhooks {
344
+ /**
345
+ * Verify a webhook signature and return the typed event payload.
346
+ *
347
+ * @param payload Raw request body (string or Buffer).
348
+ * @param headers Object containing `svix-id`, `svix-timestamp`, `svix-signature`.
349
+ * @param secret Webhook signing secret (`whsec_...` from the Self dashboard).
350
+ * @returns Parsed and verified {@link WebhookEvent}.
351
+ * @throws `WebhookVerificationError` if the signature is invalid.
352
+ * @throws `ZodError` if the payload shape doesn't match any known event type.
353
+ */
354
+ static verify(payload: string | Buffer, headers: WebhookHeaders | Record<string, string>, secret: string): WebhookEvent;
355
+ }
356
+
357
+ interface ApiErrorBody {
358
+ code: string;
359
+ message: string;
360
+ details?: Record<string, unknown>;
361
+ }
362
+ /**
363
+ * Thrown when the Self API returns a non-2xx response.
364
+ * Wraps the standard `{ error: { code, message, details? } }` envelope.
365
+ */
366
+ declare class SelfApiError extends Error {
367
+ readonly statusCode: number;
368
+ readonly code: string;
369
+ readonly details: Record<string, unknown> | undefined;
370
+ constructor(statusCode: number, body: ApiErrorBody);
371
+ }
372
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1,341 @@
1
+ // src/errors.ts
2
+ import { WebhookVerificationError } from "svix";
3
+ var SelfApiError = class extends Error {
4
+ statusCode;
5
+ code;
6
+ details;
7
+ constructor(statusCode, body) {
8
+ super(body.message);
9
+ this.name = "SelfApiError";
10
+ this.statusCode = statusCode;
11
+ this.code = body.code;
12
+ this.details = body.details;
13
+ }
14
+ };
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)}`);
65
+ }
66
+ };
67
+
68
+ // src/webhooks.ts
69
+ import { z as z6 } from "zod";
70
+ import { Webhook } from "svix";
71
+
72
+ // ../schemas/dist/stripe-events.js
73
+ import { z } from "zod";
74
+ var stripeEventBase = z.object({
75
+ id: z.string().startsWith("evt_"),
76
+ type: z.string(),
77
+ created: z.number(),
78
+ data: z.object({ object: z.unknown() })
79
+ });
80
+ var chargeRefunded = stripeEventBase.extend({
81
+ type: z.literal("charge.refunded"),
82
+ data: z.object({
83
+ object: z.object({
84
+ id: z.string().startsWith("ch_"),
85
+ customer: z.string().startsWith("cus_").nullable(),
86
+ amount_refunded: z.number()
87
+ })
88
+ })
89
+ });
90
+ var invoicePaid = stripeEventBase.extend({
91
+ type: z.literal("invoice.paid"),
92
+ data: z.object({
93
+ object: z.object({
94
+ id: z.string().startsWith("in_"),
95
+ customer: z.string().startsWith("cus_"),
96
+ // API version 2026-04-22.dahlia moved the subscription id from the
97
+ // top-level `subscription` field to `parent.subscription_details.subscription`.
98
+ // We accept BOTH: routes will prefer the new location and fall back
99
+ // to the legacy field, so a downgrade to an older API version stays
100
+ // safe.
101
+ subscription: z.string().startsWith("sub_").nullable().optional(),
102
+ parent: z.object({
103
+ subscription_details: z.object({
104
+ subscription: z.string().startsWith("sub_").nullable().optional()
105
+ }).nullable().optional()
106
+ }).nullable().optional(),
107
+ period_end: z.number().int(),
108
+ billing_reason: z.string().optional(),
109
+ lines: z.object({
110
+ data: z.array(z.object({
111
+ // Each line item also has its own subscription details + price
112
+ // in the new API. The route reads price metadata from here.
113
+ price: z.object({
114
+ id: z.string(),
115
+ metadata: z.record(z.string(), z.string()).optional()
116
+ }).nullable().optional(),
117
+ pricing: z.object({
118
+ price_details: z.object({
119
+ price: z.string()
120
+ }).nullable().optional()
121
+ }).nullable().optional(),
122
+ // API version 2026-04-22.dahlia: the invoice's top-level
123
+ // `period_start`/`period_end` collapsed to invoice-creation
124
+ // time on single-line subscription invoices; the real billing
125
+ // cycle lives ONLY here on the line item. Route reads
126
+ // `period.end` for the credit-grant `expires_at`.
127
+ period: z.object({
128
+ start: z.number().int(),
129
+ end: z.number().int()
130
+ }).optional()
131
+ })).min(1)
132
+ })
133
+ })
134
+ })
135
+ });
136
+ var subscriptionData = z.object({
137
+ id: z.string().startsWith("sub_"),
138
+ customer: z.string().startsWith("cus_"),
139
+ status: z.string(),
140
+ // API version 2026-04-22.dahlia moved `current_period_end` from the
141
+ // subscription top level onto each subscription item (items.data[].current_period_end).
142
+ // Accept both — route handler prefers the new per-item location and
143
+ // falls back to the legacy field.
144
+ current_period_end: z.number().int().optional(),
145
+ cancel_at_period_end: z.boolean().optional(),
146
+ items: z.object({
147
+ data: z.array(z.object({
148
+ current_period_end: z.number().int().optional(),
149
+ current_period_start: z.number().int().optional(),
150
+ price: z.object({
151
+ id: z.string(),
152
+ recurring: z.object({ interval: z.enum(["month", "year"]) }).nullable().optional()
153
+ })
154
+ })).min(1)
155
+ })
156
+ });
157
+ var customerSubscriptionCreated = stripeEventBase.extend({
158
+ type: z.literal("customer.subscription.created"),
159
+ data: z.object({ object: subscriptionData })
160
+ });
161
+ var customerSubscriptionUpdated = stripeEventBase.extend({
162
+ type: z.literal("customer.subscription.updated"),
163
+ data: z.object({ object: subscriptionData })
164
+ });
165
+ var customerSubscriptionDeleted = stripeEventBase.extend({
166
+ type: z.literal("customer.subscription.deleted"),
167
+ data: z.object({ object: subscriptionData })
168
+ });
169
+ var handledStripeEvent = z.discriminatedUnion("type", [
170
+ chargeRefunded,
171
+ invoicePaid,
172
+ customerSubscriptionCreated,
173
+ customerSubscriptionUpdated,
174
+ customerSubscriptionDeleted
175
+ ]);
176
+
177
+ // ../schemas/dist/metronome-events.js
178
+ import { z as z2 } from "zod";
179
+ var creditCreate = z2.object({
180
+ id: z2.string(),
181
+ type: z2.literal("credit.create"),
182
+ timestamp: z2.string().datetime(),
183
+ environment_type: z2.string().optional(),
184
+ credit_id: z2.string(),
185
+ customer_id: z2.string(),
186
+ // Populated for contract-scoped credits; absent/null for standalone
187
+ // CreditGrants (the path Self currently uses).
188
+ contract_id: z2.string().nullable().optional(),
189
+ parent_recurring_credit_id: z2.string().nullable().optional(),
190
+ // Custom-field buckets are scattered by entity type. The `source` tag
191
+ // we set on grant creation lands under `credit_custom_fields`. Used to
192
+ // distinguish lifetime grants (Free signup, Enterprise admin) from
193
+ // periodic grants (Stripe invoice.paid). Periodic grants are applied
194
+ // to Firestore inline by the invoice.paid handler; the credit-create
195
+ // handler skips them so the same balance change isn't double-applied.
196
+ // Absence of the tag defaults to lifetime (the safe-direction fallback).
197
+ credit_custom_fields: z2.object({
198
+ source: z2.enum(["lifetime", "periodic"]).optional()
199
+ }).passthrough().optional(),
200
+ contract_custom_fields: z2.record(z2.string(), z2.string()).optional(),
201
+ customer_custom_fields: z2.record(z2.string(), z2.string()).optional()
202
+ });
203
+ var commitCreate = z2.object({
204
+ id: z2.string(),
205
+ type: z2.literal("commit.create"),
206
+ timestamp: z2.string().datetime(),
207
+ environment_type: z2.string().optional(),
208
+ commit_id: z2.string(),
209
+ customer_id: z2.string(),
210
+ contract_id: z2.string().nullable().optional(),
211
+ commit_custom_fields: z2.object({
212
+ source: z2.enum(["lifetime", "periodic"]).optional()
213
+ }).passthrough().optional(),
214
+ contract_custom_fields: z2.record(z2.string(), z2.string()).optional(),
215
+ customer_custom_fields: z2.record(z2.string(), z2.string()).optional()
216
+ });
217
+ var handledMetronomeEvent = z2.discriminatedUnion("type", [creditCreate, commitCreate]);
218
+
219
+ // ../schemas/dist/webhook-events.js
220
+ import { z as z3 } from "zod";
221
+ var verificationCompleted = z3.object({
222
+ type: z3.literal("verification.completed"),
223
+ verification_id: z3.string(),
224
+ external_uuid: z3.string(),
225
+ flow_id: z3.string(),
226
+ flow_version_id: z3.string(),
227
+ environment: z3.enum(["test", "live"]),
228
+ status: z3.enum(["valid", "invalid", "error", "expired"]),
229
+ proof_attributes: z3.record(z3.string(), z3.unknown()),
230
+ /** Raw ZK proof JSON. Present when status is 'valid'; null otherwise. */
231
+ proof: z3.record(z3.string(), z3.unknown()).nullable(),
232
+ verified_at: z3.string().datetime(),
233
+ storage_state: z3.enum(["pending", "committed", "failed"]),
234
+ storage_uri: z3.string().nullable()
235
+ });
236
+ var verificationStorageCommitted = z3.object({
237
+ type: z3.literal("verification.storage_committed"),
238
+ verification_id: z3.string(),
239
+ external_uuid: z3.string(),
240
+ storage_uri: z3.string(),
241
+ credential_id: z3.string(),
242
+ committed_at: z3.string().datetime()
243
+ });
244
+ var verificationStorageFailed = z3.object({
245
+ type: z3.literal("verification.storage_failed"),
246
+ verification_id: z3.string(),
247
+ external_uuid: z3.string(),
248
+ error: z3.string(),
249
+ failed_at: z3.string().datetime()
250
+ });
251
+ var webhookEvent = z3.discriminatedUnion("type", [
252
+ verificationCompleted,
253
+ verificationStorageCommitted,
254
+ verificationStorageFailed
255
+ ]);
256
+
257
+ // ../schemas/dist/session-requests.js
258
+ import { z as z4 } from "zod";
259
+ var createSessionBody = z4.object({
260
+ flowId: z4.string().uuid(),
261
+ externalUuid: z4.string().min(1).max(128),
262
+ expiresInSeconds: z4.number().int().min(60).max(86400).default(3600),
263
+ metadata: z4.record(z4.unknown()).optional().refine((val) => !val || JSON.stringify(val).length <= 4096, {
264
+ message: "metadata must be 4KB or less when serialized"
265
+ }),
266
+ successUrl: z4.string().url().optional(),
267
+ failureUrl: z4.string().url().optional()
268
+ });
269
+
270
+ // ../schemas/dist/session-responses.js
271
+ import { z as z5 } from "zod";
272
+ var createSessionResponse = z5.object({
273
+ id: z5.string(),
274
+ externalUuid: z5.string(),
275
+ status: z5.literal("pending"),
276
+ flowVersionId: z5.string(),
277
+ /** Single-use token (e.g. `verify_live_<random>`). */
278
+ verificationToken: z5.string(),
279
+ /** Full URL to redirect the end-user to (e.g. `https://verify.self.xyz/s/<token>`). */
280
+ verificationUrl: z5.string(),
281
+ createdAt: z5.string().datetime(),
282
+ expiresAt: z5.string().datetime(),
283
+ completedAt: z5.null()
284
+ });
285
+ var sessionDetailResponse = z5.object({
286
+ id: z5.string(),
287
+ status: z5.enum(["pending", "valid", "invalid", "error", "expired"]),
288
+ createdAt: z5.string().datetime(),
289
+ completedAt: z5.string().datetime().nullable(),
290
+ expiresAt: z5.string().datetime(),
291
+ flowVersionId: z5.string(),
292
+ externalUuid: z5.string(),
293
+ metadata: z5.record(z5.string(), z5.unknown()).nullable(),
294
+ predicatesConfig: z5.record(z5.string(), z5.unknown()).nullable(),
295
+ proofAttributes: z5.record(z5.string(), z5.unknown()).nullable(),
296
+ storage: z5.object({
297
+ state: z5.enum(["pending", "committed", "failed"]),
298
+ uri: z5.string().nullable(),
299
+ credentialId: z5.string().nullable()
300
+ })
301
+ });
302
+
303
+ // src/webhooks.ts
304
+ var forwardCompatibleEvent = z6.discriminatedUnion("type", [
305
+ verificationCompleted.passthrough(),
306
+ verificationStorageCommitted.passthrough(),
307
+ verificationStorageFailed.passthrough()
308
+ ]);
309
+ var SelfWebhooks = class {
310
+ /**
311
+ * Verify a webhook signature and return the typed event payload.
312
+ *
313
+ * @param payload Raw request body (string or Buffer).
314
+ * @param headers Object containing `svix-id`, `svix-timestamp`, `svix-signature`.
315
+ * @param secret Webhook signing secret (`whsec_...` from the Self dashboard).
316
+ * @returns Parsed and verified {@link WebhookEvent}.
317
+ * @throws `WebhookVerificationError` if the signature is invalid.
318
+ * @throws `ZodError` if the payload shape doesn't match any known event type.
319
+ */
320
+ static verify(payload, headers, secret) {
321
+ const wh = new Webhook(secret);
322
+ const verified = wh.verify(
323
+ typeof payload === "string" ? payload : payload.toString("utf-8"),
324
+ headers
325
+ );
326
+ return forwardCompatibleEvent.parse(verified);
327
+ }
328
+ };
329
+ export {
330
+ SelfApiError,
331
+ SelfClient,
332
+ SelfWebhooks,
333
+ WebhookVerificationError,
334
+ createSessionBody,
335
+ createSessionResponse,
336
+ sessionDetailResponse,
337
+ verificationCompleted,
338
+ verificationStorageCommitted,
339
+ verificationStorageFailed,
340
+ webhookEvent
341
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
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.",
5
+ "license": "BUSL-1.1",
6
+ "homepage": "https://self.xyz",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/selfxyz/self-dashboard.git",
10
+ "directory": "packages/self-enterprise-sdk"
11
+ },
12
+ "keywords": [
13
+ "self",
14
+ "selfxyz",
15
+ "identity",
16
+ "verification",
17
+ "zk",
18
+ "passport",
19
+ "sdk"
20
+ ],
21
+ "type": "module",
22
+ "main": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "publishConfig": {
25
+ "name": "@selfxyz/enterprise-sdk",
26
+ "main": "dist/index.js",
27
+ "types": "dist/index.d.ts",
28
+ "access": "public"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "engines": {
36
+ "node": ">=20.0.0"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "type-check": "tsc -p tsconfig.json --noEmit",
41
+ "test": "vitest run",
42
+ "lint": "eslint src",
43
+ "lint:fix": "eslint src --fix",
44
+ "format": "prettier --write \"**/*.{ts,tsx,js,jsx,mjs,cjs,json,md,yaml,yml}\" --ignore-path ../../.prettierignore",
45
+ "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mjs,cjs,json,md,yaml,yml}\" --ignore-path ../../.prettierignore",
46
+ "clean": "rm -rf dist .turbo *.tsbuildinfo"
47
+ },
48
+ "dependencies": {
49
+ "svix": "^1.92.2",
50
+ "zod": "^3.24.1"
51
+ },
52
+ "devDependencies": {
53
+ "@self/schemas": "workspace:*",
54
+ "@types/node": "^22.10.2",
55
+ "tsup": "^8.4.0",
56
+ "typescript": "^5.7.2",
57
+ "vitest": "^2.1.8"
58
+ }
59
+ }