Skip to main content

Overview

Embedded biometric sessions are managed end-to-end by Gu1. When the user completes capture in the hosted UI, Gu1 persists the session, applies your organization policies (face-match score thresholds, cross-entity checks, billing), and emits webhooks with the session result. Your integration should treat payload.status as the biometric outcome β€” the same field returned by GET /api/kyc/biometric/sessions/:id. Gu1 exposes two delivery paths:
  1. Organization webhooks β€” configure in Webhook configuration and subscribe to biometric.session_* events (all status transitions).
  2. Per-request webhookUrl β€” optional HTTPS URL passed in POST /api/kyc/biometric/sessions; Gu1 POSTs on terminal capture outcomes (approved, rejected, abandoned, expired). Manual cancel via API triggers organization webhooks only.

Event types

EventWhen
biometric.session_createdSession created (status: pending)
biometric.session_in_progressUser started capture in the hosted UI
biometric.session_approvedBiometric session approved by Gu1
biometric.session_rejectedBiometric session rejected by Gu1
biometric.session_abandonedUser left the hosted flow without finishing
biometric.session_cancelledSession cancelled in Gu1 (pending / in_progress only)
biometric.session_expiredSession expired

Payload envelope

Same outer structure as KYC events:
{
  "event": "biometric.session_approved",
  "timestamp": "2026-06-11T15:00:00Z",
  "organizationId": "org-uuid",
  "payload": { }
}
The inner payload is the biometric session as stored in Gu1 (same shape as the REST API), plus an entity snapshot when available.

Payload fields

payload.status
string
required
Biometric session status: pending, in_progress, approved, rejected, abandoned, cancelled, expired.
payload.rejectionCode
string | null
Present when status is rejected (e.g. FACE_MATCH_SCORE_LOW, CROSS_ENTITY, CAPTURE_DECLINED).
payload.rejectionMessage
string | null
Human-readable detail for rejectionCode when available.
payload.mode
string
Always face_match for new sessions (hosted liveness + KYC portrait comparison).
payload.sessionUrl
string | null
Hosted capture URL while the session is active (pending / in_progress).
payload.hostedSessionId
string
Opaque ID of the session in the hosted capture environment (support/correlation only; use id as your primary Gu1 session key).
payload.decision
object
Provider capture payload after completion (liveness, face match scores, media keys). Fetch images via GET /api/kyc/biometric/sessions/:id/media?key=.
payload.entity
object
Snapshot of the person entity (id, externalId, name, type) when available.

Example: session created

{
  "event": "biometric.session_created",
  "timestamp": "2026-06-11T14:00:00Z",
  "organizationId": "org-uuid",
  "payload": {
    "id": "bio-session-uuid",
    "entityId": "entity-uuid",
    "organizationId": "org-uuid",
    "kycValidationId": "kyc-validation-uuid",
    "status": "pending",
    "mode": "face_match",
    "sessionUrl": "https://verify.example.com/session/abc",
    "hostedSessionId": "hosted-session-id",
    "iframeAllow": "camera; microphone; fullscreen; autoplay; encrypted-media",
    "rejectionCode": null,
    "rejectionMessage": null,
    "createdAt": "2026-06-11T14:00:00.000Z",
    "updatedAt": "2026-06-11T14:00:00.000Z",
    "completedAt": null,
    "entity": {
      "id": "entity-uuid",
      "externalId": "customer-123",
      "name": "Jane Doe",
      "type": "person"
    }
  }
}

Example: in progress

{
  "event": "biometric.session_in_progress",
  "timestamp": "2026-06-11T14:05:00Z",
  "organizationId": "org-uuid",
  "payload": {
    "id": "bio-session-uuid",
    "entityId": "entity-uuid",
    "status": "in_progress",
    "mode": "face_match",
    "sessionUrl": "https://verify.example.com/session/abc",
    "rejectionCode": null,
    "rejectionMessage": null
  }
}

Example: approved

{
  "event": "biometric.session_approved",
  "timestamp": "2026-06-11T14:10:00Z",
  "organizationId": "org-uuid",
  "payload": {
    "id": "bio-session-uuid",
    "entityId": "entity-uuid",
    "status": "approved",
    "mode": "face_match",
    "sessionUrl": "https://verify.example.com/session/abc",
    "rejectionCode": null,
    "rejectionMessage": null,
    "completedAt": "2026-06-11T14:10:00.000Z",
    "decision": {
      "face_match": {
        "status": "Approved",
        "score": 99.2
      },
      "liveness": {
        "status": "Approved",
        "score": 98.5
      }
    },
    "entity": {
      "id": "entity-uuid",
      "externalId": "customer-123",
      "name": "Jane Doe",
      "type": "person"
    }
  }
}

Example: rejected

{
  "event": "biometric.session_rejected",
  "timestamp": "2026-06-11T14:12:00Z",
  "organizationId": "org-uuid",
  "payload": {
    "id": "bio-session-uuid",
    "entityId": "entity-uuid",
    "status": "rejected",
    "mode": "face_match",
    "rejectionCode": "FACE_MATCH_SCORE_LOW",
    "rejectionMessage": "Face match score 28.5 is below organization threshold 30",
    "completedAt": "2026-06-11T14:12:00.000Z",
    "decision": {
      "face_match": {
        "status": "Approved",
        "score": 28.5
      }
    },
    "entity": {
      "id": "entity-uuid",
      "externalId": "customer-123",
      "name": "Jane Doe",
      "type": "person"
    }
  }
}

Common rejectionCode values

CodeMeaning
FACE_MATCH_SCORE_LOWFace match score below organization threshold (face_match mode)
CROSS_ENTITYCapture matches a different entity in your organization
CAPTURE_DECLINEDCapture declined in the hosted flow

Per-request webhookUrl

When you pass webhookUrl at session creation:
  • Body uses the same event names and payload shape as organization webhooks.
  • Requests are signed with HMAC-SHA256 in X-Webhook-Signature when your KYC webhook secret is configured.
  • Headers also include X-Webhook-Event, X-Webhook-ID, and X-Webhook-Timestamp.
Verify signatures the same way as outbound webhooks.
Manual cancel (POST /api/kyc/biometric/sessions/:id/cancel) emits biometric.session_cancelled on organization webhooks only; it does not POST to per-request webhookUrl.