Hızlı başlangıç
Bu sayfa en yaygın akışı, Türk kimlik kartı (ön ve arka yüz) ve ardından canlılık adımını baştan sona kurar. Her adım, ayrıntılı anlatıldığı sayfaya bağlantı verir.
Bir entegrasyon üç parçadan oluşur:
- Sunucunuz Prove ID’ye giriş yapar, oturum açmış kullanıcınız için işlemi başlatır ve erişim token’ı ile işlem kimliğini uygulamaya döner.
- Uygulamanız çekim adımlarını Prove ID iOS SDK ile çalıştırır ve her birini bu token ile Prove ID’ye gönderir.
- Sunucunuz işlemin o kullanıcıya ait olduğunu kontrol eder, sonucu Prove ID’den okur ve kararı verir.
Başlamadan önce pod’u, ProveIDApiClient paketini ve kamera iznini Kurulum sayfasında anlatıldığı gibi ekleyin.
1. Oturumu sunucunuzda başlatın
Bölüm başlığı “1. Oturumu sunucunuzda başlatın”Yalnızca uygulamanızda oturum açmış kullanıcıların çağırabileceği kendi uç noktanızı ekleyin; örneğin POST /api/kyc/mobile-session. Bu uç nokta:
POST /oauth/access_tokenile erişim token’ı alır (form-encoded,client_credentialsgrant) veexpires_indolmadan kısa süre öncesine kadar önbellekte tutar.- Belge türünü ve adımları belirterek
POST /id/startile işlemi başlatır. transactionIddeğerini kullanıcınızla ilişkilendirerek kaydeder.- Uygulamaya
accessToken,transactionIdvegestureTypedöner.
Erişim token’ı cihaza ulaştığı için mobil akışta, yalnızca doğrulama adımlarının gerektirdiği yetkilere sahip ayrı bir Prove ID API kullanıcısı kullanın. Bu kullanıcının tanımlanması için Prove ID ekibine başvurun.
// Node.js 18+ (built-in fetch), Expressconst API = process.env.PROVEID_API ?? 'https://test.api.proveid.io'; // canlı: https://api.proveid.io
let cached; // { token, expiresAt }
async function accessToken() { if (cached && cached.expiresAt > Date.now() + 60_000) return cached.token; const res = await fetch(`${API}/oauth/access_token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.PROVEID_CLIENT_ID, client_secret: process.env.PROVEID_CLIENT_SECRET, }), }); if (!res.ok) throw new Error(`Prove ID sign-in failed: ${res.status}`); const data = await res.json(); cached = { token: data.access_token, expiresAt: Date.now() + data.expires_in * 1000 }; return cached.token;}
// requireUser kendi kimlik doğrulama middleware'iniz; db kendi veri katmanınız.app.post('/api/kyc/mobile-session', requireUser, async (req, res) => { const token = await accessToken(); const start = await fetch(`${API}/id/start`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ optionalData: { idType: 'IDCARD', controls: ['FRONT', 'BACK', 'LIVENESS'] }, }), }); if (!start.ok) return res.status(502).end(); const { transactionId, gestureType } = await start.json();
await db.kycSessions.insert({ transactionId, userId: req.user.id }); res.set('Cache-Control', 'no-store').json({ accessToken: token, transactionId, gestureType });});import osimport time
import requests
API = os.environ.get("PROVEID_API", "https://test.api.proveid.io") # production: https://api.proveid.io_cached = {"token": None, "expires_at": 0.0}
def access_token(): if _cached["token"] and _cached["expires_at"] > time.time() + 60: return _cached["token"] res = requests.post( f"{API}/oauth/access_token", data={ # form-encoded "grant_type": "client_credentials", "client_id": os.environ["PROVEID_CLIENT_ID"], "client_secret": os.environ["PROVEID_CLIENT_SECRET"], }, timeout=30, ) res.raise_for_status() data = res.json() _cached.update(token=data["access_token"], expires_at=time.time() + data["expires_in"]) return _cached["token"]
def start_mobile_session(user_id): """Yalnızca oturum açmış kullanıcıların erişebildiği bir view'dan çağırın; yanıtı Cache-Control: no-store ile gönderin.""" token = access_token() res = requests.post( f"{API}/id/start", headers={"Authorization": f"Bearer {token}"}, json={"optionalData": {"idType": "IDCARD", "controls": ["FRONT", "BACK", "LIVENESS"]}}, timeout=30, ) res.raise_for_status() data = res.json() KycSession.objects.create(transaction_id=data["transactionId"], user_id=user_id) # kendi modeliniz return {"accessToken": token, "transactionId": data["transactionId"], "gestureType": data.get("gestureType")}Hologram veya NFC adımını eklemek için controls listesine HOLOGRAM veya NFC ekleyin; bkz. İşlem başlatma.
2. Uygulamada SDK’yı hazırlayın
Bölüm başlığı “2. Uygulamada SDK’yı hazırlayın”Uygulama açılırken SDK’yı ve servis istemcisini bir kez Prove ID’ye yönlendirin:
import RKYC_iOSimport ProveIDApiClient
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let serverUrl = "https://test.api.proveid.io" // canlı: https://api.proveid.io EndpointManager.SERVER_URL = serverUrl ProveIDApiClientAPI.basePath = serverUrl return true}Kullanıcı doğrulamayı başlattığında kendi oturumunuzla uç noktanızı çağırın ve token’ı yalnızca bellekte tutun:
import Foundationimport ProveIDApiClient
struct KYCSessionInfo { let transactionId: String let gestureType: String}
enum KYCSession { static let backend = URL(string: "https://your-backend.example.com")!
/// Doğrulamayı sunucunuzda başlatır ve SDK çağrıları için Prove ID token'ını ayarlar. static func begin(completion: @escaping (Result<KYCSessionInfo, Error>) -> Void) { var request = URLRequest(url: backend.appendingPathComponent("api/kyc/mobile-session")) request.httpMethod = "POST" request.setValue("Bearer \(YourAuth.sessionToken)", forHTTPHeaderField: "Authorization") // uygulamanızın kendi oturumu
URLSession.shared.dataTask(with: request) { data, response, error in guard error == nil, (response as? HTTPURLResponse)?.statusCode == 200, let data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let token = json["accessToken"] as? String, let transactionId = json["transactionId"] as? String else { completion(.failure(error ?? URLError(.badServerResponse))) return } ProveIDApiClientAPI.customHeaders["Authorization"] = "Bearer \(token)" let gestureType = json["gestureType"] as? String ?? "EYE" completion(.success(KYCSessionInfo(transactionId: transactionId, gestureType: gestureType))) }.resume() }
/// Akış bittiğinde çağırın (tamamlandı, iptal edildi veya hata oluştu). static func end() { ProveIDApiClientAPI.customHeaders.removeValue(forKey: "Authorization") }}Token’ı UserDefaults, dosya veya Keychain’de saklamayın ve log’a yazdırmayın. 401 durumunda ne yapılacağı için bkz. Kimlik doğrulama.
3. Ön ve arka yüzü çekin
Bölüm başlığı “3. Ön ve arka yüzü çekin”Tek bir view controller iki yüzü de yönetebilir: ön yüz için .NEW_ID, arka yüz için .NEW_ID_BACK algılar.
import UIKitimport ProveIDApiClientimport id_card_detection_ios_wrapperimport id_card_detection_ios_cnn
enum DocumentSide { case front, back }
final class DocumentCaptureViewController: UIViewController { @IBOutlet weak var containerView: UIView!
var side: DocumentSide = .front var transactionId = "" var onFinished: ((Result<Void, Error>) -> Void)?
private var reader: IDCardDetectionWrapper? private var isSending = false
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated)
let reader = IDCardDetectionWrapper(frame: containerView.bounds) containerView.addSubview(reader.getView()!)
let conf = IDCardDetectionWrapperConfiguration() conf.cardTypeList = [side == .front ? .NEW_ID : .NEW_ID_BACK] conf.messageToEncrypt = transactionId conf.encryptDeviceId = true conf.detectBothOrientation = true reader.setConfiguration(conf: conf)
reader.setDelegate(delegate: self) reader.setViewDelegate(delegate: self) reader.setFrameDelegate(delegate: self) reader.startCapture() self.reader = reader }
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) reader?.stopCapture() }
private func send(image: UIImage, frame: UIImage?) { guard !isSending, let png = image.pngData() else { return } isSending = true reader?.stopCapture()
let imageBase64 = png.base64EncodedString() let frameBase64 = frame?.pngData()?.base64EncodedString()
switch side { case .front: let body = CheckIDModel(transactionId: transactionId, img: imageBase64, frame: frameBase64) IDServiceAPI.checkNewIDCard(body: body) { [weak self] result in self?.finish(result.map { _ in () }) } case .back: let body = SecondaryIDInformationModel(image: imageBase64, frame: frameBase64, transactionID: transactionId) IDServiceAPI.addSecondary(body: body) { [weak self] result in // NFC adımını sonradan eklerseniz capturedData sno, bdat ve edat değerlerini taşır self?.finish(result.map { _ in () }) } } }
private func finish(_ result: Result<Void, ErrorResponse>) { DispatchQueue.main.async { self.isSending = false self.onFinished?(result.mapError { $0 as Error }) } }}
extension DocumentCaptureViewController: IDCardDetectionWrapperDelegate, IDCardDetectionWrapperViewDelegate, IDCardDetectionWrapperFrameDelegate { func cardDetected(cardImage: UIImage, cardType: WrapperCardType, frame: UIImage) { send(image: cardImage, frame: frame) }
func cardDetected(image: UIImage, cardType: WrapperCardType) { // Kullanılmıyor: frame delegate ayarlandığında bunun yerine yukarıdaki metot çağrılır. }
func cardDetectionFailed(wrapperErrorsModel: WrapperErrorsModel) { // Kullanıcıyı yönlendirin: yaklaşın, yansımayı önleyin vb. Bkz. Hatalar. }
func onViewDrawn(mainRect: CGRect, photoRect: CGRect?) { }}Önce side = .front ile gösterin; onFinished başarılı dönünce side = .back ile tekrar gösterin. Bir çağrı başarısız olursa kullanıcının yeniden denemesine izin verin (bkz. Hatalar). Diğer seçenekler: Belge çekimi.
4. Canlılık
Bölüm başlığı “4. Canlılık”- adımdan gelen
gestureTypedeğerini SDK karşılığına çevirin ve videoyu kaydedin:
import UIKitimport RKYC_iOSimport ProveIDApiClient
final class LivenessViewController: UIViewController { @IBOutlet weak var containerView: UIView!
var transactionId = "" var gestureType = "EYE" var onFinished: ((Result<Void, Error>) -> Void)?
private let livenessController = LivenessController()
override func viewDidLoad() { super.viewDidLoad()
LivenessController.FACE_DETECTION_LAYER_SHAPE = .OVAL LivenessController.CONTROL_FACE_CAPTURE = true LivenessController.STOP_VIDEO_WHILE_RECORDING = true LivenessController.IS_GESTURE_CONTROL_ENABLED = true LivenessController.VIDEO_PREPARATION_TIME_IN_SECONDS = 3 LivenessController.VIDEO_TIME_IN_SECONDS = 5
switch gestureType { case "MOUTH": LivenessController.GESTURE_TYPE = .MOUTH case "HEAD-VERTICAL": LivenessController.GESTURE_TYPE = .HEAD_VERTICAL case "HEAD-HORIZONTAL": LivenessController.GESTURE_TYPE = .HEAD_HORIZONTAL case let value where value.hasPrefix("MULTIDIR_"): let directions = value.dropFirst("MULTIDIR_".count).split(separator: "-") LivenessController.GESTURE_TYPE = .RANDOMIZED_MULTI_GESTURE LivenessController.RANDOMIZED_GESTURES = directions.compactMap { HeadPosition(string: String($0)) } LivenessController.VIDEO_PREPARATION_TIME_IN_SECONDS = 0 default: LivenessController.GESTURE_TYPE = .EYE }
livenessController.setDelegate(self) livenessController.setGestureDelegate(self) containerView.addSubview(livenessController.view) }
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) livenessController.startCapture() }
private func send(videoURL: URL, rotate: Int) { defer { try? FileManager.default.removeItem(at: videoURL) } // videoyu cihazda asla bırakmayın guard let video = try? Data(contentsOf: videoURL) else { return }
let body = FaceVerificationModel(transactionId: transactionId, video: video.base64EncodedString(), rotate: rotate) FaceServiceAPI.verifyFace(body: body) { [weak self] result in DispatchQueue.main.async { self?.onFinished?(result.map { _ in () }.mapError { $0 as Error }) } } }}
extension LivenessViewController: LivenessDelegate, GestureDelegate { func gestureControlled(gestureModel: GestureModel) { livenessController.stopCapture() send(videoURL: gestureModel.videoURL, rotate: gestureModel.rotate) }
func onVideoCaptured(videoURL: URL, rotate: Int) { } func onVideoStarting() { /* geri sayım gösterin */ } func onVideoStarted() { } func onVideoCanceled() { /* yüz kadrajdan çıktı; kullanıcıdan tekrar denemesini isteyin */ } func onFaceDetectionFailure(isAway: Bool) { } func onFaceDetectionSuccess() { } func onFaceDetectionIdle() { } func onMultiFaceDetected() { /* kadrajda yalnızca bir kişi olmalı */ }
func positionFailed(model: PositionFailedModel) { } func positionSucceed(model: PositionSuccedModel) { } func positionCorrect() { } func preparationPositionControlled(model: PreparationPositionModel) { }}Pasif canlılık, fotoğraftan canlılık ve kimlik kartıyla canlılık Canlılık sayfasında anlatılır.
5. Sunucunuzda tamamlayın
Bölüm başlığı “5. Sunucunuzda tamamlayın”Canlılık başarılı olunca KYCSession.end() çağırın ve transactionId değerini uygulamanızın oturumuyla kendi uç noktanıza gönderin; örneğin POST /api/kyc/mobile-session/complete. Kullanıcıyı uygulamada onaylamayın: uygulama yalnızca adımların bittiğini bilir.
Sunucunuz önce bu transactionId değerinin 1. adımda bu kullanıcı için kaydedildiğini kontrol eder, ardından sonucu GET /id/id-info-with-tid/{transactionId} ile Prove ID’den okur ve kendi kuralınızı uygular. Aşağıdaki örnek doğrulamayı yalnızca tüm kontroller geçtiyse kabul eder; kendi risk politikanıza göre uyarlayın.
app.post('/api/kyc/mobile-session/complete', requireUser, async (req, res) => { const { transactionId } = req.body; const session = await db.kycSessions.findOne({ transactionId, userId: req.user.id }); if (!session) return res.status(404).end(); // bu kullanıcının işlemi değil
const result = await fetch(`${API}/id/id-info-with-tid/${encodeURIComponent(transactionId)}`, { headers: { Authorization: `Bearer ${await accessToken()}` }, }); if (!result.ok) return res.status(502).end(); const info = await result.json(); const controls = info.controlResults ?? []; const passed = controls.length > 0 && controls.every((c) => c.entryInterpretation === true);
await db.kycSessions.update({ transactionId }, { passed }); res.json({ passed });});from urllib.parse import quote
def complete_mobile_session(user_id, transaction_id): session = KycSession.objects.filter(transaction_id=transaction_id, user_id=user_id).first() if session is None: raise PermissionError("Bu kullanıcının işlemi değil")
res = requests.get( f"{API}/id/id-info-with-tid/{quote(transaction_id, safe='')}", headers={"Authorization": f"Bearer {access_token()}"}, timeout=30, ) res.raise_for_status() controls = res.json().get("controlResults") or [] passed = bool(controls) and all(c.get("entryInterpretation") is True for c in controls)
session.passed = passed session.save() return passedSonraki adımlar
Bölüm başlığı “Sonraki adımlar”- Daha güçlü kontroller için hologram veya NFC çip okuma ekleyin.
- Hatalar sayfasındaki her durum için yol gösteren mesajlar gösterin.
- Yayına almadan önce Canlıya çıkış kontrol listesini gözden geçirin.
- Sık sorulan sorular SSS sayfasında yanıtlanır.