Enrollment Flow
Create an Emblem pass after your provider verification succeeds.
What Enrollment Is
Enrollment lets a user create an Emblem pass after your provider has already verified them. The user opts in, completes passkey creation on Emblem, and can skip verification next time through assertion.
When To Use It
Use enrollment after:
- Your own verification flow completed successfully.
- You have the verification facts you need.
- The user chooses to create an Emblem pass.
Enrollment is not automatic. It is an explicit post-verification opt-in flow.
Prerequisites
You need:
- A provisioned
client_id - A registered
redirect_uri - A secret API key (
emb_sk_...)
The same client_id used for assertion can be used for enrollment, but it must
also be provisioned with credential issuance authority.
End-to-End Flow
Your verification completes successfully
User opts in to "Create an Emblem pass"
Your server → POST /api/v1/credentials/enroll/start → receives enrollment_url
Browser → redirects to Emblem → user creates passkey
Browser → returns to your redirect_uri → with status or errorStart Enrollment
POST /api/v1/credentials/enroll/start records verification provenance and
creates the enrollment session in one call.
import crypto from 'node:crypto'
import { createClient } from '@emblemapp/sdk'
const emblem = createClient({
apiKey: process.env.EMBLEM_SECRET_KEY!,
})
const enrollment = await emblem.startEnrollment({
client_id: process.env.EMBLEM_ASSERTION_CLIENT_ID!,
redirect_uri: 'https://provider.example/emblem/enroll/callback',
state: crypto.randomUUID(),
provider: 'SAFEPASSAGE',
verification_level: 'L1',
external_verification_id: 'provider-verification-123',
verified_at: new Date().toISOString(),
external_user_ref: 'user-123',
external_session_ref: 'journey-456',
})
res.redirect(enrollment.enrollment_url)Request fields
| Field | Required | Description |
|---|---|---|
client_id | Yes | Your provisioned authorization client |
redirect_uri | Yes | Callback URI to receive the final enrollment outcome |
state | Yes | Your CSRF or correlation value |
provider | Yes | Verification provider enum, currently SAFEPASSAGE, PRIVATEAV, or EMBLEM_LEGACY |
verification_level | Yes | Verification level satisfied by your verification: L1 or L2 |
external_verification_id | Yes | Immutable provider-side verification identifier |
verified_at | Yes | ISO-8601 timestamp for when your verification completed |
external_user_ref | No | Your user reference |
external_session_ref | No | Your journey or session reference |
Response fields
| Field | Description |
|---|---|
enrollment_url | Redirect target for the Emblem-hosted passkey flow |
correlation_id | Trace identifier for support and logs |
expires_at | Session expiry timestamp. Enrollment sessions last 10 minutes |
One call records provenance and starts enrollment
Enrollment start is both the provenance-recording step and the browser-session creation step. There is no separate issuance API call in the Connect flow.
Callback Handling
Enrollment is terminal on the browser callback. There is no back-channel code exchange step.
Success callback
https://provider.example/emblem/enroll/callback?status=success&state=opaque_stateFailure callback
https://provider.example/emblem/enroll/callback?error=cancelled&error_code=USER_CANCELLED&state=opaque_stateCallback outcomes
| Query params | Meaning |
|---|---|
status=success, state | The user completed passkey creation successfully |
error, error_code, state | The flow ended without successful enrollment |
Current callback error mappings
error | error_code | Meaning |
|---|---|---|
expired | SESSION_EXPIRED | Enrollment session expired |
already_used | SESSION_ALREADY_USED | Enrollment link was already consumed |
unavailable | CLIENT_UNAVAILABLE | Client is unavailable for enrollment |
cancelled | USER_CANCELLED | User cancelled the flow |
not_supported | PASSKEY_NOT_SUPPORTED | Browser or platform does not support passkeys |
already_registered | ALREADY_REGISTERED | The passkey already exists |
registration_failed | REGISTRATION_FAILED | Passkey registration failed |
invalid_request | INVALID_REQUEST | The enrollment flow could not continue because the request was invalid |
Always validate state on callback before treating the flow as complete.
Idempotency
Enrollment start is idempotent on external_verification_id within the same
client_id.
- If you retry with the same
external_verification_idand the same immutable verification details, Emblem reuses the stored provenance and returns a freshenrollment_url. - If you retry with conflicting verification details, Emblem returns
409 IDEMPOTENCY_CONFLICT.
Treat external_verification_id as immutable per completed verification.
Error Reference
Start enrollment errors
| Code | HTTP status | Meaning | What to do |
|---|---|---|---|
VALIDATION_ERROR | 400 | Request body failed validation | Check required fields, enums, and ISO-8601 timestamps |
INVALID_REDIRECT_URI | 400 | redirect_uri is not registered for the client, or it already contains reserved enrollment callback params | Register the exact URI you send and exclude state, status, error, and error_code |
UNAUTHORIZED | 401 | Missing or invalid secret key | Check Authorization: Bearer emb_sk_... |
PUBLISHER_SUSPENDED | 403 | Tenant is suspended by Emblem | Contact Emblem support or your account owner |
CLIENT_INACTIVE | 403 | Authorization client is inactive | Ask Emblem to activate the client |
ISSUANCE_NOT_ALLOWED | 403 | Client is not provisioned for credential issuance | Ask Emblem to enable enrollment authority for the client |
NOT_FOUND | 404 | Authorization client not found | Verify client_id and environment alignment |
RATE_LIMITED | 429 | Too many requests | Back off and honor Retry-After |
IDEMPOTENCY_CONFLICT | 409 | The same external_verification_id was reused with conflicting details | Treat the verification tuple as immutable |
INTERNAL_ERROR | 500 | Emblem-side failure | Retry if appropriate and log request_id / correlation_id |
Notes
redirect_urimust be registered on the authorization client.redirect_urimust not already includestate,status,error, orerror_codequery params. Emblem owns those callback keys and will reject ambiguous enrollment callback URIs.- Connect endpoints use secret-key server auth only.
- Enrollment requires separate issuance authority even if the same client is already approved for assertion.