Skip to main content

Quickstart

This walks through a complete end-to-end verification: a Go backend mints a session token, a Flutter mobile app consumes it, the user completes a passive liveness check, and the backend reads the result. Expect to spend ~5 minutes.

Prerequisites

  • Go 1.21+ installed locally
  • Flutter 3.13+ with a connected device or running emulator
  • An eKYC tenant. If you don't have one yet, request access. You'll receive dv_sk_test_... and dv_sk_live_... API keys.

1. Mint a session on your server

The sk_ API key stays on your server; you never ship it to a mobile app. The server's job is to mint short-lived session tokens (dv_tok_* JWTs, ~30 minute TTL) that the mobile app uses for one verification session.

Install the Go SDK:

go get github.com/cloudbhutan/ekyc-sdk-go/v2

Server stub (cmd/auth-server/main.go):

package main

import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"time"

ekyc "github.com/cloudbhutan/ekyc-sdk-go/v2"
)

func main() {
client, err := ekyc.NewClient(ekyc.Config{
BaseURL: "https://api.drukverify.com",
APIKey: os.Getenv("EKYC_SECRET_KEY"),
})
if err != nil {
log.Fatal(err)
}

http.HandleFunc("/session", func(w http.ResponseWriter, r *http.Request) {
ref := r.URL.Query().Get("user")
if ref == "" {
http.Error(w, "user query param required", 400)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()

s, err := client.Sessions.Create(ctx, ekyc.CreateSessionRequest{
CustomerRef: ref,
TTL: 30 * time.Minute,
})
if err != nil {
http.Error(w, err.Error(), 500)
return
}
json.NewEncoder(w).Encode(map[string]string{"token": s.Token})
})

log.Println("listening on :9090")
log.Fatal(http.ListenAndServe(":9090", nil))
}

Run it:

EKYC_SECRET_KEY=dv_sk_test_… go run ./cmd/auth-server

Quick smoke from a different terminal:

curl 'http://localhost:9090/session?user=demo'
# → {"token":"dv_tok_test_eyJhbGciOi..."}

2. Wire the Flutter app

Add the SDK to pubspec.yaml. The Flutter SDK ships from a private GitHub repo — ask your integration contact for access if you don't already have it.

dependencies:
drukverify_flutter:
git:
url: git@github.com:DrukVerify/drukverify-flutter.git
ref: v2.2.0
drukverify_flutter_generated:
git:
url: git@github.com:DrukVerify/drukverify-flutter.git
ref: v2.2.0
path: lib/src/_generated

In your app's startup code:

import 'package:dio/dio.dart';
import 'package:drukverify_flutter/drukverify_flutter.dart';

final client = EkycClient(
config: EkycConfig(
host: 'https://api.drukverify.com',
apiKey: 'pk_test_…', // your public tenant key
tokenProvider: () async {
final res = await Dio().get<Map<String, dynamic>>(
'http://10.0.2.2:9090/session', // Android emulator → host
queryParameters: {'user': 'demo'},
);
return res.data!['token'] as String;
},
),
);

tokenProvider is called lazily — the first authenticated request triggers a fetch from your backend. The SDK caches the result and auto-refreshes on 401 expired_token.

3. Run a liveness check

In a screen widget:

import 'package:drukverify_flutter/drukverify_flutter.dart';

EkycLivenessWidget(
client: client,
onResult: (LivenessResult result) {
if (result.error != null) {
print('liveness error: ${result.error}');
return;
}
print('isLive=${result.isLive} '
'lc_id=${result.serverResponse?['lc_id']}');
},
);

The widget owns the camera preview, runs ML Kit face detection on-device to gate challenges, captures the verification frame, and POSTs to /v1/liveness/check. The result you see (is_live, score, lc_id) is the server's verdict — on-device ML is a UX layer only. The challenge mix (blink / turn head / smile) is controlled via the optional config: LivenessConfig(…) prop; see the widget guide.

4. Read the result on the server

The widget's result.serverResponse['lc_id'] is a stable identifier (format lc_<32 hex>). Your server can re-fetch the result later via GET /v1/liveness/checks/:id for audit / risk review.

What's next