The core verifier flow is simple: receive a PASSID token, call the verify endpoint, get signed claim summaries, freshness, revocation status, and audit metadata. The verifier response does not include raw financial statements or transaction feeds.
Create a workspace at passid.io/onboard. You receive two API keys immediately - one for sandbox testing, one for live production. All API calls require the X-Institution-Key header.
In your institution dashboard -> API & Webhooks, paste the URL on your server that will receive PASSID events. This is required before going live.
PASSID ships two packages. Install both - @passidio/react for your frontend and @passidio/node for your backend. The React package gives you <PassidCodeVerification /> - a compact inline field that embeds directly in your application forms (loan, onboarding, KYC) and calls onVerified(result) after the customer's code is redeemed and verified. The Node package keeps the API key server-side, verifies tokens, and returns signed claim summaries plus audit context.
apiKey prop on <PassidCodeVerification /> is fine for sandboxing, but in production use a backend proxy or route the key through @passidio/node so it is never exposed in your frontend bundle.Add <PassidCodeVerification /> directly inside an existing form - loan application, onboarding, KYC, rental. It renders as a compact inline widget. The customer opens their PASSID app, taps Share, reads out the one-time code, and onVerified(result) fires after token verification. No redirect, no new tab, no session dashboard.
| Prop | Type | Required | Description |
|---|---|---|---|
| apiKey | string | Yes | Your institution live or sandbox API key |
| environment | "sandbox" | "live" | No | Defaults to "sandbox". In sandbox mode demo codes are shown inline for testing. |
| baseUrl | string | No | PASSID backend base URL. Defaults to https://api.passid.io |
| requestedClaims | string[] | No | Claims to request. Defaults to standard claim set. See Claims Reference below. |
| onVerified | (result) => void | No | Called when code is redeemed. Receives full CodeVerificationResult - claims are in result.verified_claims. |
| onError | (Error) => void | No | Called on network errors or unexpected API failures. |
| theme.primary | string | No | Hex colour for buttons and accent. Defaults to #00c27a. |
After the frontend collects the credential, your backend verifies it. Use passidMiddleware (Express) or withPassid (Next.js) to verify the token and enforce a policy in one line. Claims attach to req.passid (Express) or are passed as the second argument (Next.js).
| Option | Type | Effect on request |
|---|---|---|
| requireClaims | string[] | Rejects if any listed claim is missing or false - e.g. ["income_verified","sanctions_screening_clear"] |
| maxCredentialAgeHours | number | Rejects credentials older than this many hours - enforces freshness requirement -> 403 POLICY_VIOLATION |
| requireFraudChecksPassed | boolean | Rejects if identity_and_fraud_checks_passed is false -> 403 POLICY_VIOLATION |
| requireSanctionsClear | boolean | Rejects if sanctions_screening_clear is false - always recommended for financial applications -> 403 POLICY_VIOLATION |
For in-branch or kiosk scenarios where the customer is physically present and can scan a QR code. Use <PASSIDVerify /> from the same package - it renders a QR overlay and fires onSuccess(claims) when the customer scans and approves in the mobile app. For embedded form use cases, prefer <PassidCodeVerification /> from Step 2.
POST /v1/sessions with your API key and requested permissions. The backend creates a pending session and returns requestId, code (e.g. AB3X-7YQM), deepLink (passid://verify-request?r=...&i=...), and expiresAt. TTL defaults to 10 minutes.<canvas> element using the qrcode library - no third-party image service. An 8-char fallback code (AB3X-7YQM) is shown alongside. A countdown timer starts. The SDK polls GET /v1/sessions/:requestId every 2.5 seconds.GET /v1/sessions/:id/public (no auth required) to confirm the request is still pending and show the expiry countdown. The customer taps Share & Verify. The app generates a one-time share token from their verified claims and calls POST /v1/sessions/:id/fulfill with their user JWT.status = "completed", and the SDK poll detects this on its next tick. onSuccess(claims) fires with the verified claims object. A verification.completed webhook is also sent to your registered endpoint.ttlSeconds), the poll returns status: "expired" and onDecline("Request expired") fires. Show a "Generate new code" button.| State | Trigger | What the user sees |
|---|---|---|
| idle | Initial mount | Generate QR button + manual entry input |
| requesting | QR button clicked | Spinner - "Creating verification session..." |
| awaiting | Session created | QR code + 8-char code + countdown + pulse dot |
| verifying | Manual token submitted | Spinner - "Verifying credential..." |
| success | Poll resolves / token verified | Verified claims summary + freshness + provenance |
| declined | Customer declined / expired / invalid | Decline message + friendly reason + try again |
| error | Network / API failure | Error message + try again |
The primary embedded flow. The customer opens their PASSID mobile app, taps Share, and reads out a short one-time code (e.g. KE8V-3TXY-Z72L). The institution types or pastes it into the <PassidCodeVerification /> field - or calls the redemption API directly from the backend.
KE8V-3TXY-Z72L. It is a one-time-use token bound to the credential claims the customer chose to share. Expires after first use.<PassidCodeVerification /> accepts the code, shows verification status, and fires onVerified(result) with verified claims. Works over the phone, in person, or fully remote - no camera needed.POST /api/bridge/code/redeemThe verified_claims object returned in onVerified(result) and in the data.verified_claims field on API responses. Treat it as a verified summary for your policy workflow, not as raw account data.
permissions[] array| Value | Type | Claims returned |
|---|---|---|
| verified_credential_summary | object | Full standard claim set - all verified claims in one request |
| income_verified | boolean + string | income_verified, income_band |
| identity_verified | boolean | identity_verified |
| sanctions_clear | boolean | sanctions_screening_clear - OFAC · EU · UN |
| payment_reliability | boolean | payment_behavior_verified |
| savings_consistency | boolean | savings_behavior_verified |
| fraud_risk | boolean | identity_and_fraud_checks_passed |
If you are not using the React SDK (e.g. server-side integration, mobile backend, non-React frontend), call POST /v1/verify directly from your backend after receiving the applicant's token.
Idempotency-Key: <unique-id> so retries on network failure do not double-bill or double-verify. Response header: X-PASSID-API-Version: 1.
requestId, code, deepLink, expiresAt. Body: { institutionName?, permissions?, ttlSeconds? } - TTL default 600s, max 3600s.
status: "pending" | "completed" | "declined" | "expired". When completed, includes full claims and verifiedAt. Requires institution API key.
410 if expired, fulfilled, or declined.
AB3X-7YQM or AB3X7YQM) to the same response as the public endpoint above. Used by the mobile app manual entry screen.
PASSID sends signed HTTP POST events to your registered endpoint. Register your URL in the institution dashboard -> API & Webhooks. All events use the same envelope format.
POST /v1/verify call. Payload includes verified claims, proof type, and the token. Use this on your backend to update your application state.deny_reason (e.g. TOKEN_EXPIRED, HARD_DENY_SANCTIONS_MATCH). Only fired when the request carries a valid institution API key.POST /v1/outcome. Payload includes the token, outcome type, product type, amount, and verification context at time of outcome reporting. Use for audit trails and downstream integrations.PASSID retries non-2xx responses automatically: immediately -> 30 s -> 5 min -> 30 min -> 2 h (5 attempts total). Pending deliveries survive server restarts. Your endpoint must return 200 within 10 seconds - do your async processing in the background.
Every webhook is signed with HMAC-SHA256 using the webhook secret you configured. Always verify the signature before processing - never trust the payload without it.
express.raw() (not express.json()) when reading the body for HMAC verification. JSON parsing modifies whitespace and may alter the bytes used for the signature.Weeks or months after a PASSID-verified application, report what actually happened. This closes the verification coverage loop - PASSID uses your outcomes to improve claim coverage and signal normalization across corridors. It also gives you cohort analytics in your institution dashboard.
X-Institution-Key. One outcome per token per institution - duplicate reports return 409. The verification context at time of outcome reporting is captured automatically - you never send it.
| Value | Meaning |
|---|---|
| repaid | Loan repaid in full, on schedule |
| no_default | Still active, no missed payments to date |
| delinquent | 30+ days past due, not yet defaulted |
| defaulted | 90+ days past due or written off |
| fraud_confirmed | Identity or application fraud confirmed |
| account_closed | Account closed by institution (not default) |
| other | Outcome not covered by above - use notes |
After reporting outcomes, view calibration data in your institution dashboard -> Performance tab. The cohort API is also available directly:
Use your sandbox API key (pk_sandbox_...) with the same endpoints. All sandbox responses are deterministic - same token always returns the same claims. Isolated from live data.
Use these codes with POST /api/bridge/code/redeem or type them into <PassidCodeVerification environment="sandbox" /> - they appear as clickable chips in sandbox mode automatically.
| Code | Coverage | Corridor | Result |
|---|---|---|---|
| KE8V-3TXY-Z72L | High coverage | KE->GB | income ✓ · identity ✓ · sanctions clear · fraud checks passed · verified |
| NG4M-XQRS-T88W | High coverage | NG->US | income ✓ · identity ✓ · sanctions clear · fraud checks passed · verified |
| GH2P-ABCD-56KL | Partial coverage | GH->EU | income ✓ · identity ! unverified · fraud checks incomplete · manual review required |
<PassidCodeVerification apiKey="pk_sandbox_..." environment="sandbox" />onSuccess on your page.webhook.test payload to your endpoint so you can validate signature verification before going live.Complete all items before switching to your live API key.
.env committed to version controlX-PASSID-Signature: sha256=... + HMAC-SHA256 + timingSafeEqual200 within 10 seconds - async processing done in backgroundIdempotency-Key header sent on every POST /v1/verify call to prevent double-verification on retryHARD_DENY_SANCTIONS_MATCH deny reason handled - do not approve applications with this reason under any circumstancesonDecline("Request expired") shows a "Generate new code" button, not an erroronSuccess received, webhook received and signature verified