@snapkyc-ooru/consent-handoff-react
v0.1.5
Published
React SDK: agreement consent flow with QR or intent handoff + txn polling.
Readme
@snapkyc-ooru/consent-handoff-react
KYC consent & identity handoff flow for React — agreement → consent → QR / deeplink → polling → verified.
What is this?
@snapkyc-ooru/consent-handoff-react is a plug-and-play React + TypeScript SDK that orchestrates the full SnapKYC identity-verification handoff inside your product — without you needing to manage a single API call or UI state machine.
Drop in a single component (or call one function) and your users move through:
Integration agreement → Purpose consent → QR code / app deeplink
→ Live status polling → Verification summaryYour backend mints a short-lived integrationToken; the SDK handles everything else.
Install
npm install @snapkyc-ooru/consent-handoff-react react react-domPeer dependencies: React >= 17, react-dom >= 17
Styles
Import the bundled stylesheet once — typically in your app root or entry file:
import "@snapkyc-ooru/consent-handoff-react/styles.css";All design tokens are scoped to
.snap-consent-handoff. Override them via thethemeprop rather than targeting:rootin your host app.
The 5-step flow
| Step | Screen |
|------|--------|
| 1 | Agreement — integration notice card; optional custom title |
| 2 | Consent — purpose checkboxes with Cancel / I Agree |
| 3 | Handoff — QR scan or Continue with Aadhaar App (Android intent deeplink) |
| 4 | Polling — live status until terminal result |
| 5 | Success — status strip, confirmed purposes, demographic accordions; Done |
Dismiss controls (×, Cancel, Done) all call onRequestClose and cleanly abort any in-flight work.
Quick start
Option A — Imperative modal (recommended)
Mounts outside your React tree on document.body. Ideal for portals, dashboards, or any flow that should not disrupt your existing layout.
import { useCallback, useRef } from "react";
import {
openSnapConsentHandoffFlow,
type CompletionResult,
type FlowEvent,
} from "@snapkyc-ooru/consent-handoff-react";
import "@snapkyc-ooru/consent-handoff-react/styles.css";
function VerifyButton() {
const disposeRef = useRef<(() => void) | null>(null);
const startVerification = useCallback(async () => {
const integrationToken = await fetchTokenFromYourBackend(); // short-lived token
disposeRef.current?.();
disposeRef.current = openSnapConsentHandoffFlow({
integrationToken,
rpContext: {
reference_id: "user-or-order-id",
session_id: "optional-correlation-id",
},
handoffMode: "qr", // or "intent"
title: "Loan Application KYC Verification",
pollIntervalMs: 1500,
pollDeadlineMs: 120_000,
theme: {
primary: "#0f766e",
borderActive: "#0f766e",
surfaceMuted: "#f0fdfa",
},
onFlowEvent(evt: FlowEvent) {
console.log("phase →", evt);
// agreement-loading | agreement-ready | txn-created
// polling | completed | failed | cancelled
},
onSuccess(result: CompletionResult) {
// result.txn_id — transaction identifier
// result.status — terminal status string
// result.raw — full API payload (demographics, purposes, evidence)
},
onError(err) {
console.error(err);
},
}).dispose;
}, []);
return (
<button type="button" onClick={() => void startVerification()}>
Verify identity
</button>
);
}Important: store the returned dispose reference and call it on component unmount or navigation. The modal's built-in dismiss buttons do this automatically when using openSnapConsentHandoffFlow.
openSnapConsentQrFlowis a deprecated alias — useopenSnapConsentHandoffFlowgoing forward.
Option B — Declarative component
Embed the flow inline inside a wizard step, settings page, or your own modal shell.
Inline (embedded)
import { useState } from "react";
import {
SnapConsentHandoffFlow,
type CompletionResult,
} from "@snapkyc-ooru/consent-handoff-react";
import "@snapkyc-ooru/consent-handoff-react/styles.css";
export function KycStep({ token }: { token: string }) {
const [hidden, setHidden] = useState(false);
if (hidden) return null;
return (
<SnapConsentHandoffFlow
integrationToken={token}
rpContext={{ reference_id: "USER-123", session_id: "sess-abc" }}
handoffMode="intent"
displayMode="inline"
title="Loan Application KYC Verification"
acceptLanguage="en"
theme={{ primary: "#0f766e" }}
onSuccess={(result: CompletionResult) => {
// result.raw contains demographics, purposes, evidence
}}
onRequestClose={() => setHidden(true)}
/>
);
}Inside your own modal shell
<SnapConsentHandoffFlow
integrationToken={token}
rpContext={{ reference_id: "USER-123" }}
handoffMode="qr"
displayMode="modal"
onRequestClose={() => setShowModal(false)}
/>| displayMode | Behaviour |
|---------------|-----------|
| "modal" (default) | SDK renders its own scrim + header × |
| "inline" | Borderless sheet; no scrim — sits inside your layout |
Handoff modes
| handoffMode | What happens after I Agree |
|---------------|---------------------------|
| "qr" | Generates a PNG QR code (POST …/generate-qr). User scans with the authorised app. |
| "intent" | Generates an Android app deeplink (POST …/generate-intent?platform=android). User taps Continue with Aadhaar App to open the native app; the SDK polls status until success. |
Callbacks
| Callback | Fires when |
|----------|-----------|
| onFlowEvent(evt) | On every phase transition (see event names below) |
| onSuccess(result) | Terminal success — receives { txn_id?, status, raw } |
| onError(err) | Agreement load failure or unrecoverable error |
| onRequestClose() | User dismissed via ×, Cancel, or Done |
FlowEvent values
agreement-loading → agreement-ready → txn-initiating → txn-created
→ handoff-loading → handoff-ready → polling → completed | failed | cancelledPost-success helpers
import {
normalizeTxnStatusPayload,
extractDemographicData,
demographicAccordionSections,
} from "@snapkyc-ooru/consent-handoff-react";Theme
All overrides are optional. Unspecified keys fall back to the built-in warm default (orange primary, cream accents). Setting primary alone automatically sets borderActive unless you override it separately.
import {
DEFAULT_SNAP_CONSENT_THEME,
resolveSnapConsentTheme,
type SnapConsentHandoffTheme,
} from "@snapkyc-ooru/consent-handoff-react";
const brand: Partial<SnapConsentHandoffTheme> = {
primary: "#0f766e",
surfaceMuted: "#f0fdfa",
};
<SnapConsentHandoffFlow {...props} theme={brand} />;| Token | Controls |
|-------|----------|
| primary | Brand accent — I Agree / Done, checkboxes, active borders |
| onPrimary | Text on primary buttons |
| canvas | Sheet background |
| surface / surfaceMuted | Card surfaces (unselected / selected rows, notice) |
| text / textMuted | Body and secondary text |
| border / borderActive | Default and emphasized borders |
| modalOverlay | Modal scrim |
| danger | Error text |
| success | Success hero and status checkmarks |
| successStatusSurface | Status summary card on success |
| successPurposesSurface | Confirmed purposes panel |
| successAccordionBorder | Demographic accordion borders |
| radius / fontFamily / gap / maxWidth / modalZ | Layout and typography |
Props reference
Required props
| Prop | Type | Description |
|------|------|-------------|
| integrationToken | string | Authorization: Token … sent on every SDK request |
| rpContext.reference_id | string | Sent as rp_context.ref_id when initiating a transaction |
| handoffMode | "qr" \| "intent" | Handoff delivery mechanism |
Optional props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| rpContext.session_id | string | — | Optional correlation metadata for your backend |
| acceptLanguage | string | — | Accept-Language header + agreement locale preference |
| agreementId | string | — | Reserved; agreement is implied by the integration token |
| qrRequestBody | { size?: number } | — | Extra QR params; txn_id is always injected by SDK |
| pollIntervalMs | number | 1500 | Polling interval in milliseconds |
| pollDeadlineMs | number | 120000 | Polling timeout in milliseconds |
| displayMode | "modal" \| "inline" | "modal" | Modal (with scrim) or inline sheet |
| title | string | Agreement name | Dialog heading |
| theme | Partial<SnapConsentHandoffTheme> | Built-in warm default | Visual overrides |
| intentPlatformOverride | "android" \| "ios" | — | Force platform when already known |
| userAgent | string | — | Override UA for tests or SSR |
| requestGeolocation | boolean | true | Prompt for browser geolocation on load; sends coords in rp_context on initiate when granted |
| geolocationOptions | PositionOptions | SDK defaults | Options for getCurrentPosition (timeout, accuracy, etc.) |
| onFlowEvent | (evt: FlowEvent) => void | — | Phase transition callback |
| onSuccess | (result: CompletionResult) => void | — | Terminal success callback |
| onError | (err: unknown) => void | — | Error callback |
| onRequestClose | () => void | — | Dismiss callback (required for host-controlled hide) |
Security — credential hygiene
integrationToken is a bearer credential that lives in the browser. Follow these rules:
- ✅ Mint short-lived tokens on your backend after the user is authenticated
- ✅ Deliver the token to the client just before calling
openSnapConsentHandoffFlow - ❌ Never embed long-lived secrets in static bundles or environment variables shipped to the browser
HTTP contract
All route assumptions are documented in docs/HTTP_CONTRACT.md.
