From the open-source SDK
On this page
- Why migrate
- Concept mapping
- Step-by-step
- 1. Map your predicate config to a flow
- 2. Replace SelfBackendVerifier with SelfClient
- 3. Replace inline verification with a webhook handler
- 4. Drop your frontend QR component
- 5. Delete the old code paths
- What stays the same
- What’s better
- What’s different
- Rolling out safely
If you’ve integrated the open-source Self Pass frontend SDK (@selfxyz/qrcode) and backend verifier (@selfxyz/core), here’s how it maps to Self Enterprise.
Why migrate
The open-source SDK requires you to:
- Stand up an off-chain verifier yourself (or run it in-process and pay the cold-start cost).
- Manage a
ConfigStorefor predicate configurations. - Build your own webhook delivery, retry, and signature scheme.
- Write your own audit log.
- Operate it all with no SLA.
Enterprise replaces all of that with a managed service. Your frontend integration stays largely identical; the backend collapses.
Concept mapping
| Open-source self-pass | Self Enterprise |
|---|---|
SelfAppBuilder (frontend) | Same idea, but the endpoint becomes our hosted verifier, you don’t host it. |
SelfBackendVerifier | Replaced by our managed verifier. You don’t run this anymore. |
ConfigStore (your IConfigStorage impl) | Replaced by the dashboard’s flow configuration. |
| Predicate config object | A flow in the dashboard. flowId replaces inline config. |
| Per-app verifier secrets | A Bearer API key (sk_live_…). |
| Custom webhook code | A webhook endpoint you register, verified via SelfWebhooks.verify(...). |
Step-by-step
1. Map your predicate config to a flow
Today (open-source):
import { AllIds, DefaultConfigStore, SelfBackendVerifier } from '@selfxyz/core';
const verifier = new SelfBackendVerifier(
'my-app', // scope
'https://myapp.com/verify', // endpoint
false, // mock passports?
AllIds, // accepted document types
new DefaultConfigStore({ minimumAge: 18, excludedCountries: ['IRN'], ofac: true }),
'uuid', // user-identifier type
);
With Enterprise:
- In the dashboard, create a flow.
- Configure the same rules: minimum age 18, exclude
IRN, OFAC on. - Deploy it. Copy the
flowIdfrom the Test or Live tab.
2. Replace SelfBackendVerifier with SelfClient
Today:
import { SelfBackendVerifier } from '@selfxyz/core';
const verifier = new SelfBackendVerifier(/* scope, endpoint, ... */);
app.post('/verify', async (req, res) => {
const result = await verifier.verify(
req.body.attestationId,
req.body.proof,
req.body.publicSignals,
req.body.userContextData,
);
// ...act on result
});
With Enterprise:
import { SelfClient } from '@selfxyz/enterprise-sdk';
const self = new SelfClient({ apiKey: process.env.SELF_API_KEY! });
// When the user wants to verify (e.g. app/api/start-verification/route.ts):
export async function POST(req: Request) {
const { userId } = await req.json();
const session = await self.sessions.create({
flowId: process.env.SELF_FLOW_ID!,
externalUuid: userId,
});
return Response.json({ verificationUrl: session.verificationUrl });
}
// You no longer need a /verify endpoint, we deliver the result via webhook.
3. Replace inline verification with a webhook handler
Today your /verify endpoint runs the Groth16 verifier and acts on the result inline.
With Enterprise, register a webhook endpoint in the dashboard (it receives all events) and handle verification.completed:
// app/api/webhooks/self/route.ts
import { SelfWebhooks } from '@selfxyz/enterprise-sdk';
export async function POST(req: Request) {
const event = SelfWebhooks.verify(
await req.text(),
Object.fromEntries(req.headers),
process.env.SELF_WEBHOOK_SECRET!,
);
if (event.type === 'verification.completed' && event.status === 'valid') {
grantAccess(event.external_uuid, event.proof_attributes);
}
return new Response('ok', { status: 200 });
}
external_uuid is what you passed when creating the session (typically your user ID). See Verify webhooks.
4. Drop your frontend QR component
With Enterprise you don’t render your own QR. Wherever you used @selfxyz/qrcode, call self.sessions.create(...) on your backend and redirect the user to session.verificationUrl instead; Self’s hosted page renders the QR and handles deeplinks. Then remove @selfxyz/qrcode.
5. Delete the old code paths
- Remove
@selfxyz/coreand@selfxyz/qrcode. - Remove your
ConfigStoreimplementation. - Remove your
/verifyroute, Enterprise delivers via webhook. - Remove any code that loaded verifier circuit files at boot.
What stays the same
- The Self mobile app is unchanged for your users.
- The disclosures are the same, age, nationality, OFAC, etc. See Disclosures.
- The proof system is the same under the hood.
- Your frontend gets simpler: send the user to the hosted
verificationUrl, no QR rendering required.
What’s better
- No verifier infrastructure to run.
- Audit log out of the box.
- Webhook delivery with automatic retries.
- A dashboard to change rules without a redeploy.
- Per-flow versioning, audit exactly what a user was verified against last March.
What’s different
- You pay per verification. See Plans.
- Your service no longer holds the raw proof, we verify it and hand you the attributes. (You can still read the raw proof from the
verification.completedevent if you truly need it.)
Rolling out safely
- Build the Enterprise integration behind a feature flag (
enterprise: false). - Configure a test-environment flow + webhook endpoint.
- QA against mock passports.
- Move staging to Enterprise.
- Cut over prod with a percentage rollout (10% → 50% → 100%).
- Decommission your verifier infra after a week of clean prod traffic.
Thanks — what went wrong?
Thanks for your feedback!