Перейти к содержанию

KYC Service — Integrations

Inbound: Auth Center (Serverpod)

Endpoint: POST /api/v1/process

Authentication: X-Service-Token: {KYC_SERVICE_SECRET} header — must match the KYC_SERVICE_SECRET env var exactly. Returns 401 if missing or wrong.

Request body:

{
  "userId": 123,
  "kycId": 456,
  "passportKey": "kyc/456/passport.jpg",
  "selfieKey": "kyc/456/selfie.jpg",
  "stampKey": "kyc/456/stamp.jpg"
}

stampKey is optional. userId, kycId, passportKey, selfieKey are all required.

Response: 202 Accepted

{
  "jobId": "kyc-456",
  "status": "queued"
}

If the job kyc-{kycId} already exists in the queue (duplicate submission), returns 202 with the same jobId without re-enqueuing.

Health check: GET /health200 { "status": "ok" }

Garage / S3 (Image Storage)

Uses @aws-sdk/client-s3 + @aws-sdk/s3-request-presigner to generate presigned GET URLs that expire in 5 minutes. These URLs are passed directly to the AI providers.

Env var Purpose
S3_ENDPOINT Garage endpoint (e.g., https://s3.1-wallet.app)
S3_REGION Region identifier (e.g., 1-wallet)
S3_ACCESS_KEY Access key ID
S3_SECRET_KEY Secret access key
S3_BUCKET Bucket name (default: kyc-data)

forcePathStyle: true is required for Garage compatibility.

Gemini AI (OCR)

  • SDK: @google/generative-ai
  • Model: gemini-3-flash-preview (for both OCR and face match fallback)
  • Auth: GEMINI_API_KEY env var
  • The model receives a presigned URL image as an inline data part (base64 encoded in the request)
  • OCR prompt (OCR_PROMPT in src/services/prompts.js): instructs the model to extract passport fields and return structured JSON
  • Response is parsed as JSON after stripping any markdown code fences
  • 429 rate limit errors are caught and reraised with retry delay embedded in the error message

CompreFace (Face Matching)

CompreFace is a self-hosted face recognition server using ArcFace embeddings.

  • Endpoint: POST {COMPREFACE_URL}/api/v1/verification/verify
  • Auth: x-api-key: {COMPREFACE_API_KEY} header
  • Request: multipart/form-data with source_image (passport photo) and target_image (selfie)
  • Response: JSON with result[0].face_matches[0].similarity — cosine similarity score 0.0–1.0
  • Health check at startup: GET {COMPREFACE_URL}/healthcheck (warning only if unreachable — does not block startup)

Error mapping: - No face in passport photo → INVALID_PHOTO:passport:... (UnrecoverableError — no retry) - No face in selfie → INVALID_PHOTO:selfie:... (UnrecoverableError — no retry) - Score below threshold → FACE_MISMATCH:{score}:... (UnrecoverableError — no retry) - Network error / timeout (15s) → retryable Error

CompreFace requires a named Docker volume (compreface_data) to persist face data across restarts.

Env var Purpose
COMPREFACE_URL CompreFace base URL (default: http://localhost:8000)
COMPREFACE_API_KEY API key for the verification service
FACE_MATCH_THRESHOLD Minimum similarity to accept (default: 0.75)

PostgreSQL (Results Storage)

Connection via POSTGRES_URL env var (single connection string).

Table written: kyc_verification (in the public schema, owned by Serverpod)

Operation SQL When
Success UPDATE kyc_verification SET status='ocr_complete', "ocrResult"=$1, "reviewedAt"=NOW() WHERE id=$2 After successful OCR + face match
Failure UPDATE kyc_verification SET status='ocr_failed', "ocrResult"=$1, "reviewedAt"=NOW() WHERE id=$2 After all retries exhausted or unrecoverable error
Watchdog Same as failure with failureCode='processing_timeout' Records stuck in in_review for > KYC_STUCK_TIMEOUT_MIN minutes

ocrResult is a bytea column containing the AES-encrypted JSON result blob.

Column names follow Serverpod camelCase convention and must be double-quoted in raw SQL.

Redis (BullMQ Queue)

  • Connection: REDIS_URL env var
  • Queue name: kyc-jobs
  • maxmemory-policy is set to noeviction at startup to prevent BullMQ job loss
  • maxRetriesPerRequest: null is required by BullMQ on the ioredis connection