Skip to content

Checking access...

KYC API

Identity verification surface across three cooperating layers: oracle-bridge adapter (provider-agnostic REST at /api/kyc/*), Verifiable Credential bridge (W3C VC Data Model v1.1 + Ed25519Signature2020 proof, SHA-256 canonical hash), and embedded frontend widget (KycWidget React component in marketing-suite).

Services: oracle-bridge · membership canister · marketing-suite (KycWidget) Providers: Didit (primary) · iDenfy (hot backup) · blockpass (reserved) Epic: KYC-001 (completed 2026-04-15)

For the full reference including endpoints, schemas, Mermaid lifecycle diagram, on-chain attestation invariants, DB schema, env vars, and the widget state machine, see the root API reference at api/kyc.md.

Quick Reference

oracle-bridge REST endpoints

MethodPathAuthPurpose
POST/api/kyc/sessionsession + CSRFCreate a verification session with the active provider
GET/api/kyc/status/:userIdsession + CSRFLatest verification row for the caller (or any user if admin)
GET/api/kyc/attestation[?user_id=]session + CSRFLatest live attestation (VC + hash + metadata)
GET/api/kyc/attestation/by-principal/:principaladmin sessionAdmin inspection by IC principal
POST/api/kyc/webhookprovider HMAC-SHA256Provider callback — drives membership + attestation bridges
POST/api/kyc/admin/retry-unsyncedadmin sessionReplay approved verifications whose canister call failed
POST/api/kyc/admin/retry-attestationsadmin sessionReplay attestations whose canister sync failed

The webhook route is mounted before express.json() and uses express.raw({ type: '*/*', limit: '1mb' }) so HMAC signature verification operates on the exact provider bytes. Re-ordering breaks verification 100%.

Membership canister methods (KYC attestation)

MethodKindAccessPurpose
set_kyc_attestationupdateoracle-bridge principal only (require_oracle_bridge)Insert or replace a 32-byte SHA-256 hash + metadata record
get_kyc_attestationquerypublicFetch the attestation (hash + metadata, no PII)
revoke_kyc_attestationupdatecontroller onlyMark revoked (row retained for audit)
is_kyc_verifiedquerypublicPredicate: exists AND not revoked AND not expired

Normalized Status Values

Every provider-native status collapses into:

typescript
type KycStatus = 'PENDING' | 'APPROVED' | 'DENIED' | 'REVIEW';

Unknown strings collapse to REVIEW (fail-closed — never silent-approve).

KycWidget Component

tsx
import KycWidget from '@/components/KycWidget';

<KycWidget
  userId={session.userId}              // required
  callbackUrl="https://.../kyc-verification"   // required
  onSuccess={(status) => { /* APPROVED */ }}
  onFailure={(reason, status) => { /* DENIED | REVIEW | null */ }}
  pollIntervalMs={3000}                 // default 3s
  maxPolls={30}                         // default 30 (≈90s)
/>

State machine: loading → ready → polling → { approved | rejected | error }. Provider postMessage origin is verified against the iframe origin; terminal states fire onSuccess / onFailure exactly once (guarded against React Strict Mode double-renders).

Hello World Co-Op DAO