Skip to content

User Service

The User Service is a FastAPI (Python) microservice that owns user profile data, orchestrates KYC (Know Your Customer) verification workflows, manages consent/preferences, and maintains an audit trail of all user-facing changes.

  • Profile & Identity: Manage user profile data (name, DOB, address, PAN, Aadhaar) with field-level encryption for PII.
  • KYC Orchestration: Integrate with third-party KYC vendors (HyperVerge, Signzy) for identity verification, liveness checks, and document validation.
  • Account State Management: Track user lifecycle states (active, suspended, frozen, closed) with transitions triggered by KYC outcomes or admin actions.
  • Consent & Preferences: Track user consents (terms, privacy, marketing) and notification preferences.
  • Audit Trail: Log all profile changes and KYC actions to an append-only audit log (ClickHouse for high-volume, PostgreSQL for transactional).
  • Event Producer: Emit user.created, user.updated, user.verified, user.frozen, user.kyc.* events.

User Service does not handle authentication (that’s Auth Service) or financial operations (that’s Wallet Service).

graph TB Client[Client App] -->|REST| UserAPI[User Service API] AuthSvc[Auth Service] -->|auth.created.v1| Kafka1[Kafka] Kafka1 --> UserSvc[User Service] UserSvc -->|Stores| PG[(PostgreSQL)] UserSvc -->|PII Encrypted| PG UserSvc -->|Audit| CH[(ClickHouse)] UserSvc -->|Events| Kafka2[Kafka] UserSvc -->|KYC| HV[HyperVerge] UserSvc -->|KYC| Signzy[Signzy] Kafka2 --> WalletSvc[Wallet Service] Kafka2 --> RiskSvc[Risk Service]
FieldTypeDescription
idUUID (PK)User ID (maps 1
to auth_accounts.id)
auth_idUUID UNIQUEFK to auth service identity
statusENUM(pending, active, suspended, frozen, closed)User lifecycle state
kyc_statusENUM(not_started, pending, in_review, approved, rejected, expired)KYC verification state
kyc_tierENUM(tier_0, tier_1, tier_2, tier_3)Verification level (affects limits)
created_atTIMESTAMPTZAccount creation time
updated_atTIMESTAMPTZLast modification time
verified_atTIMESTAMPTZWhen KYC was approved
FieldTypeDescription
idUUID (PK)Profile row ID
user_idUUID (FK)References users
first_name_encBYTEAEncrypted first name
last_name_encBYTEAEncrypted last name
date_of_birth_encBYTEAEncrypted date of birth
pan_number_encBYTEAEncrypted PAN (Indian tax ID)
aadhaar_last4_encBYTEAEncrypted last 4 digits of Aadhaar
address_encBYTEAEncrypted address JSON
phone_encBYTEAEncrypted phone number
emailCITEXTEmail (not encrypted, used for lookup)
avatar_urlTEXTProfile picture URL
updated_atTIMESTAMPTZLast modification time

Encryption: Field-level encryption using pgcrypto or application-level AES-256-GCM with KMS-managed keys.

FieldTypeDescription
idUUID (PK)KYC case ID
user_idUUID (FK)References users
vendorENUM(hyperverge, signzy)KYC vendor
vendor_case_idTEXTVendor’s internal case reference
statusENUM(initiated, documents_submitted, in_review, approved, rejected, expired)Case status
rejection_reasonTEXTReason for rejection (if applicable)
initiated_atTIMESTAMPTZWhen KYC was started
completed_atTIMESTAMPTZWhen vendor returned result
expires_atTIMESTAMPTZWhen KYC result expires
FieldTypeDescription
idUUID (PK)Artifact ID
kyc_case_idUUID (FK)References kyc_cases
artifact_typeENUM(selfie, pan_card, aadhaar_front, aadhaar_back, address_proof, liveness_video)Document type
storage_refTEXTEncrypted reference to stored artifact (S3/GCS)
hashTEXTSHA-256 hash of the artifact
uploaded_atTIMESTAMPTZUpload timestamp
FieldTypeDescription
idUUID (PK)Consent record ID
user_idUUID (FK)References users
consent_typeENUM(terms_of_service, privacy_policy, marketing, data_sharing, risk_disclosure)Type of consent
versionTEXTVersion of the document consented to
grantedBOOLEANWhether consent was granted
ip_addrINETIP from which consent was given
granted_atTIMESTAMPTZWhen consent was granted
revoked_atTIMESTAMPTZWhen consent was revoked (NULL if active)
FieldTypeDescription
user_idUUID (PK, FK)References users
email_trade_fillsBOOLEANEmail on trade fills
email_depositsBOOLEANEmail on deposits
email_withdrawalsBOOLEANEmail on withdrawals
email_security_alertsBOOLEANEmail on security events
push_enabledBOOLEANPush notifications enabled
sms_enabledBOOLEANSMS notifications enabled
updated_atTIMESTAMPTZLast update
FieldTypeDescription
event_idUUIDUnique event ID
user_idUUIDSubject user
actor_idUUIDUser or admin who performed action
actionStringprofile.updated, kyc.initiated, consent.granted, status.changed, etc.
resourceStringResource affected
old_valueStringPrevious value (JSON)
new_valueStringNew value (JSON)
ip_addrStringClient IP
timestampDateTimeEvent timestamp
Key PatternPurposeTTL
user:profile:{user_id}Cached profile (decrypted, for authorized internal use)5 min
user:kyc:status:{user_id}Cached KYC status5 min
user:rate:{user_id}Profile update rate limiterSliding window
MethodEndpointDescription
GET/v1/meGet own profile
PATCH/v1/meUpdate profile fields
POST/v1/me/consentsGrant or revoke consents
GET/v1/me/consentsGet consent status
PUT/v1/me/notification-prefsUpdate notification preferences
GET/v1/me/notification-prefsGet notification preferences
MethodEndpointDescription
POST/v1/kyc/initiateStart KYC verification flow
GET/v1/kyc/statusGet current KYC status
POST/v1/kyc/resubmitResubmit after rejection
POST/v1/kyc/webhookVendor callback (HyperVerge/Signzy)
MethodEndpointDescription
GET/internal/users/{id}Get user details (internal)
POST/internal/users/{id}/freezeFreeze user account
POST/internal/users/{id}/unfreezeUnfreeze user account
POST/internal/users/{id}/closeClose user account
GET/internal/users/{id}/auditGet audit trail
POST/admin/users/searchSearch users (admin)
POST/admin/kyc/manual-reviewManual KYC decision (admin)
TopicDescription
auth.created.v1New auth account → create user record
auth.account.deactivated.v1Auth suspension → freeze user
TopicDescription
user.created.v1New user record created
user.updated.v1Profile updated
user.verified.v1KYC approved, user verified
user.frozen.v1User account frozen
user.unfrozen.v1User account unfrozen
user.kyc.initiated.v1KYC flow started
user.kyc.updated.v1KYC status changed
  1. Consume auth.created.v1 event
  2. Create users row (status = pending, kyc_status = not_started)
  3. Create empty user_profile row
  4. Create default notification_prefs
  5. Emit user.created.v1
  6. Wallet Service consumes user.created.v1 and creates initial balance records
  1. User calls POST /v1/kyc/initiate
  2. Service creates kyc_cases row, selects vendor based on configuration
  3. Service calls vendor API (HyperVerge/Signzy) to initiate verification
  4. Return redirect URL or SDK token to client
  5. User completes verification on vendor UI (selfie, PAN, Aadhaar)
  6. Vendor sends webhook to POST /v1/kyc/webhook
  7. Service processes webhook:
    • Extract verification results
    • Store kyc_artifacts references
    • Update kyc_cases.status
    • If approved: set users.kyc_status = approved, users.kyc_tier = tier_1
    • If rejected: set users.kyc_status = rejected with reason
  8. Emit user.kyc.updated.v1
  9. If approved: emit user.verified.v1 → other services update limits
  1. Admin or Risk Service calls POST /internal/users/{id}/freeze
  2. Set users.status = frozen
  3. Emit user.frozen.v1
  4. Downstream effects: Wallet holds funds, Order Service rejects new orders
  • Purpose: Liveness detection, face match, PAN verification
  • Flow: SDK-based (mobile/web) → webhook callback
  • Documents: Selfie, PAN card image
  • Purpose: Aadhaar verification, address proof, bank account verification
  • Flow: API-based verification → webhook callback
  • Documents: Aadhaar (DigiLocker), bank statement
TierRequirementsLimits
Tier 0Email verified onlyView-only, no trading
Tier 1PAN + Selfie verifiedBasic trading limits
Tier 2Aadhaar + Address verifiedEnhanced limits
Tier 3Full verification + bankMaximum limits

All personally identifiable information is encrypted at the field level:

  • Algorithm: AES-256-GCM (via pgcrypto or application-level)
  • Key Management: Keys managed via KMS (AWS KMS / HashiCorp Vault)
  • Searchable Fields: Email is stored in cleartext CITEXT for lookup; all other PII is encrypted
  • Decryption: Only User Service has decryption keys; other services receive masked data
  • Audit: Every decryption is logged to user_audit
  • user_created_total — User records created
  • user_kyc_initiated_total{vendor} — KYC flows started by vendor
  • user_kyc_completed_total{vendor,status} — KYC completions by outcome
  • user_profile_update_total — Profile updates
  • user_frozen_total{reason} — Accounts frozen

Spans: user.create, user.update_profile, user.initiate_kyc, user.process_kyc_webhook, user.freeze, user.unfreeze

Structured JSON: user_id, action, kyc_case_id, vendor, status, trace_id. PII is never logged in cleartext.

  • PII Encryption: AES-256-GCM field-level encryption for all personal data
  • Webhook Verification: Vendor webhooks verified via HMAC signature
  • Rate Limiting: Profile updates limited to prevent abuse
  • Consent Audit: Every consent grant/revoke recorded with IP and timestamp
  • Admin Authorization: Admin endpoints require elevated JWT scope
ScenarioHandling
KYC vendor API timeoutReturn pending status; background job retries
Webhook delivery failureVendor retries; idempotent processing by vendor_case_id
PII decryption failureReturn masked data; alert ops for KMS key issue
Auth event missedEventual consistency: user creation can be triggered on first API call
MetricTarget
Profile read latency (p95)< 50 ms
KYC initiation latency (p95)< 2 seconds
Webhook processing latency (p95)< 500 ms
PII encryption/decryption< 5 ms per field
Audit log write success>= 99.9%
VariableDescriptionDefault
POSTGRES_URLPostgreSQL connection stringRequired
CLICKHOUSE_URLClickHouse for audit logsRequired
REDIS_URLRedis connection stringRequired
KAFKA_BROKERSKafka broker addressesRequired
HYPERVERGE_API_KEYHyperVerge API keyRequired (prod)
SIGNZY_API_KEYSignzy API keyRequired (prod)
PII_ENCRYPTION_KEY_IDKMS key ID for PII encryptionRequired
  • Language: Python 3.10+
  • Framework: FastAPI + Uvicorn
  • ORM: SQLAlchemy + Alembic
  • Kafka: confluent-kafka-python with Avro
  • Encryption: pgcrypto / AES-256-GCM
  • Package Manager: uv