Verifly API Dokümantasyonu

Modern, güvenli ve kolay 2FA doğrulama sistemi

Giriş

Verifly, kullanıcılarınızın telefon numaralarını veya e-postalarını doğrulamanızı sağlayan modern bir 2FA platformudur. Geleneksel OTP sistemlerinden farklı olarak, inbound verification yaklaşımı kullanır - yani kullanıcılar size kod gönderir, siz onlara göndermezsiniz.

Neden Verifly?

💰

Maliyet Etkin

Outbound SMS göndermek yerine, kullanıcı size mesaj atar

📱

Çoklu Kanal

SMS, WhatsApp, Sesli Arama, E-posta desteği

🚫

Spam Yok

SMS spam klasörü problemi ortadan kalkar

Gerçek Zamanlı

Socket.io ve Webhook desteği

Nasıl Çalışır?

  1. 1. Backend'inizde bir verification session oluşturursunuz
  2. 2. Kullanıcıya bir QR kod gösterilir
  3. 3. Kullanıcı QR kodu okutup doğrulama kodunu seçtiği yöntemle gönderir
  4. 4. Verifly doğrulama kodunu kontrol eder ve sonucu webhook ile size bildirir

Hızlı Başlangıç

ℹ️ Önkoşullar: Node.js 14+ veya PHP 7.4+ yüklü olmalı

Adım 1: Hesap Oluşturma

İlk olarak kayıt sayfasından bir hesap oluşturun. E-posta doğrulaması yaptıktan sonra dashboard'a erişebilirsiniz.

Adım 2: Uygulama Oluşturma

Dashboard'dan "Yeni Uygulama" butonuna tıklayın ve aşağıdaki bilgileri girin:

Uygulama Adı: Örn: "Benim Web Sitem"
Uygulama URL'i: Örn: https://example.com
Bildirim API: Doğrulama sonuçları bu API'ye bildirilecektir.
Servisler: SMS, WhatsApp, Arama, E-posta (istediğinizi seçin)

Adım 3: API Anahtarlarını Alma

Uygulama oluşturduktan sonra size iki anahtar verilir:

API Key (Public)

vf_abc123def456ghi789...

Frontend'de kullanılabilir, görünmesi sorun değil

Secret Key (Private)

96fd73b0c28fff2d9cfd9dc35...

⚠️ SADECE backend'de kullanın! Asla frontend'e eklemeyin.

Adım 4: İlk Verification Session Oluşturma

Backend'inizde bir verification session oluşturun. İşte Node.js örneği:

const crypto = require('crypto');
const axios = require('axios');

const API_KEY = 'vf_abc123...';  // Dashboard'dan aldığınız
const SECRET_KEY = '96fd73b0c28fff2d9cfd9dc35...'; // Dashboard'dan aldığınız

// 1. Signature oluştur (güvenlik için)
function createSignature(payload, secretKey) {
  const timestamp = Date.now().toString();
  const data = timestamp + JSON.stringify(payload);
  const signature = crypto
    .createHmac('sha256', secretKey)
    .update(data)
    .digest('hex');
  return { signature, timestamp };
}

// 2. Session oluştur
async function createVerificationSession(phone) {
  const payload = {
    phone: phone,           // Örn: '05461234567'
    methods: ['sms', 'whatsapp'], // İzin verilen metodlar
    lang: 'tr',            // Dil
    timeout: 5,            // Dakika cinsinden timeout (1-15)
    data: 'user_123_or_jwt_token' // Webhook'ta geri gönderilecek özel veri
  };
  
  const { signature, timestamp } = createSignature(payload, SECRET_KEY);
  
  const response = await axios.post(
    'https://www.verifly.net/api/verify/create',
    payload,
    {
      headers: {
        'X-API-Key': API_KEY,
        'X-Signature': signature,
        'X-Timestamp': timestamp,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return response.data;
}

// Kullanım
createVerificationSession('05461234567')
  .then(result => {
    console.log('Session ID:', result.data.sessionId);
    console.log('Iframe URL:', result.data.iframeUrl);
  })
  .catch(error => {
    console.error('Hata:', error.response.data);
  });

Adım 5: Iframe'i Frontend'e Ekleyin

Backend'den aldığınız iframeUrl'i kullanarak doğrulama arayüzünü gösterin:

<!-- HTML -->
<iframe 
  id="verifly-iframe"
  src="https://www.verifly.net/verify/iframe/SESSION_ID"
  width="100%"
  height="600"
  frameborder="0">
</iframe>

<script>
// Doğrulama sonucunu dinle
window.addEventListener('message', function(event) {
  // Güvenlik: Sadece Verifly'dan gelen mesajları kabul et
  if (event.origin !== 'https://www.verifly.net') return;
  
  const { type, sessionId, reason } = event.data;
  
  switch(type) {
    case 'VERIFICATION_SUCCESS':
      console.log('✅ Doğrulama başarılı!', sessionId);
      // Kullanıcıyı yönlendir
      window.location.href = '/dashboard';
      break;
      
    case 'VERIFICATION_FAILED':
      console.log('❌ Doğrulama başarısız!', reason);
      alert('Doğrulama başarısız: ' + reason);
      break;
      
    case 'VERIFICATION_EXPIRED':
      console.log('⏰ Doğrulama süresi doldu', sessionId);
      alert('Doğrulama süresi doldu. Lütfen tekrar deneyin.');
      break;

    case 'VERIFICATION_ABORTED':
      console.log('❌ Doğrulama kullanıcı tarafından iptal edildi', sessionId);
      alert('Doğrulama kullanıcı tarafından iptal edildi.');
      break;
  }
});
</script>

Iframe Event Tipleri

Iframe içinden parent window'a gönderilen postMessage event'leri:

VERIFICATION_SUCCESS

Doğrulama başarıyla tamamlandı.

Payload:

{ type: 'VERIFICATION_SUCCESS', sessionId: 'abc-123' }

Kullanım Senaryoları:

  • Kullanıcıyı dashboard'a yönlendir
  • Doğrulanmış kullanıcı badge'i göster
  • Backend'e ajax ile bildirim gönder
  • Kullanıcı profilini güncelle
VERIFICATION_FAILED

Doğrulama başarısız oldu (maksimum deneme sayısı aşıldı).

Payload:

{ type: 'VERIFICATION_FAILED', sessionId: 'abc-123', reason: 'Maximum attempts exceeded' }

Kullanım Senaryoları:

  • Hata mesajı göster
  • "Tekrar Dene" butonu göster
  • Yeni session oluştur
  • Alternatif doğrulama yöntemi öner
  • Destek ekibiyle iletişim seçeneği sun
VERIFICATION_EXPIRED

Session süresi doldu (varsayılan: 2 dakika).

Payload:

{ type: 'VERIFICATION_EXPIRED', sessionId: 'abc-123' }

Kullanım Senaryoları:

  • "Oturum süresi doldu" mesajı göster
  • "Yeni Doğrulama Başlat" butonu göster
  • Yeni session oluştur
  • Kullanıcıyı başlangıç sayfasına yönlendir
VERIFICATION_ABORTED

Doğrulama kullanıcı tarafından iptal edildi.

Payload:

{ type: 'VERIFICATION_ABORTED', sessionId: 'abc-123' }

Kullanım Senaryoları:

  • "Doğrulama iptal edildi" mesajı göster
  • "Yeni Doğrulama Başlat" butonu göster
  • Yeni session oluştur
  • Kullanıcıyı başlangıç sayfasına yönlendir
💡 İleri Seviye Event Yönetimi

1. Event Logger: Tüm event'leri analytics'e gönderin

2. State Management: Redux/Vuex store'da session durumunu güncelleyin

3. UI Feedback: Toast/snackbar bildirimleri gösterin

4. Retry Logic: Failed/Expired durumlarında otomatik yeni session oluşturun

5. A/B Testing: Farklı event senaryolarını test edin

Gelişmiş Örnek: React Entegrasyonu

import { useState, useEffect } from 'react';

function VerificationModal({ sessionId, iframeUrl }) {
  const [status, setStatus] = useState('pending');
  
  useEffect(() => {
    const handleMessage = (event) => {
      if (event.origin !== 'https://www.verifly.net') return;
      
      const { type, sessionId: sid } = event.data;
      
      if (sid !== sessionId) return; // Session kontrolü
      
      switch(type) {
        case 'VERIFICATION_SUCCESS':
          setStatus('success');
          // Analytics event
          gtag('event', 'verification_success', { session_id: sid });
          // Backend'e bildir
          fetch('/api/user/verify-complete', { 
            method: 'POST',
            body: JSON.stringify({ sessionId: sid })
          });
          // 2 saniye sonra kapat
          setTimeout(() => window.location.href = '/dashboard', 2000);
          break;
          
        case 'VERIFICATION_FAILED':
          setStatus('failed');
          // Analytics event
          gtag('event', 'verification_failed', { session_id: sid });
          // Hata mesajı göster
          toast.error('Doğrulama başarısız. Lütfen tekrar deneyin.');
          break;
          
        case 'VERIFICATION_EXPIRED':
          setStatus('expired');
          // Yeni session oluştur
          createNewSession();
          break;
      }
    };
    
    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, [sessionId]);
  
  return (
    <div className="modal">
      {status === 'success' && (
        <div className="success-message">✅ Doğrulama Başarılı!</div>
      )}
      <iframe src={iframeUrl} width="100%" height="600" />
    </div>
  );
}

✅ Tebrikler! İlk doğrulama sisteminizi kurdunuz. Şimdi API detaylarına geçebilirsiniz.

Custom UI / Headless API Kullanımı

Iframe kullanmak istemiyorsanız veya mobil uygulama geliştiriyorsanız, Verifly API'lerini doğrudan kullanarak kendi doğrulama UI'ınızı oluşturabilirsiniz.

📱 Kullanım Senaryoları
  • iOS/Android Mobil Uygulamaları - React Native, Flutter, Swift, Kotlin
  • Custom Web UI - Kendi tasarımınıza uygun doğrulama sayfası
  • Desktop Uygulamaları - Electron, Tauri, .NET
  • API-First Sistemler - Headless commerce, microservices

Genel Bakış

Verifly API'si tamamen headless çalışacak şekilde tasarlanmıştır. Tüm doğrulama işlemlerini API endpoint'leri üzerinden yönetebilirsiniz.

🔌 Kullanılacak API Endpoint'leri

POST
/api/verify/create

Session oluştur

GET
/api/verify/:sessionId

Session durumunu kontrol et

POST
/api/verify/:sessionId/select-method

Doğrulama yöntemi seç ve başlat

POST
/api/verify/:sessionId/cancel

Mevcut yöntemi iptal et

POST
/api/verify/:sessionId/abort

Session'ı tamamen iptal et

Akış Diyagramı

1️⃣ Session Oluştur
POST /api/verify/create
2️⃣ Method Seç (SMS/WhatsApp/Call/Email)
POST /api/verify/:sessionId/select-method
3️⃣ UI'da Bilgileri Göster
QR kod, verification code, service contact
4️⃣ Durumu Dinle
Socket.IO veya Polling (GET /api/verify/:sessionId)
✅ Doğrulama Başarılı!
status: 'verified'

Mobil Uygulama Örneği (React Native)

import React, { useState, useEffect } from 'react';
import { View, Text, Button, Image, StyleSheet, Linking } from 'react-native';

function VerificationScreen({ phone }) {
  const [sessionId, setSessionId] = useState(null);
  const [verificationData, setVerificationData] = useState(null);
  const [status, setStatus] = useState('idle'); // idle, loading, waiting, success, failed

  // 1. Session oluştur
  const createSession = async () => {
    setStatus('loading');
    
    const response = await fetch('https://www.verifly.net/api/verify/create', {
      method: 'POST',
      headers: {
        'X-API-Key': 'your_api_key',
        'X-Signature': generateSignature(...), // HMAC-SHA256
        'X-Timestamp': Date.now(),
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        phone: phone,
        methods: ['sms', 'whatsapp']
      })
    });
    
    const data = await response.json();
    setSessionId(data.data.sessionId);
  };

  // 2. Method seç
  const selectMethod = async (method) => {
    setStatus('loading');
    
    const response = await fetch(
      `https://www.verifly.net/api/verify/${sessionId}/select-method`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ method })
      }
    );
    
    const data = await response.json();
    setVerificationData(data.data);
    setStatus('waiting');
    
    // Polling başlat
    startPolling();
  };

  // 3. Status polling (Socket.IO yerine)
  const startPolling = () => {
    const interval = setInterval(async () => {
      const response = await fetch(
        `https://www.verifly.net/api/verify/${sessionId}`
      );
      const data = await response.json();
      
      if (data.data.status === 'verified') {
        clearInterval(interval);
        setStatus('success');
      } else if (data.data.status === 'failed' || data.data.status === 'expired') {
        clearInterval(interval);
        setStatus('failed');
      }
    }, 2000); // Her 2 saniyede kontrol
  };

  // 4. SMS uygulamasını aç
  const openSMSApp = () => {
    const url = `sms:${verificationData.selectedServiceContact}?body=${verificationData.verificationCode}`;
    Linking.openURL(url);
  };

  // 5. WhatsApp'ı aç
  const openWhatsApp = () => {
    const url = `whatsapp://send?phone=${verificationData.selectedServiceContact}&text=${verificationData.verificationCode}`;
    Linking.openURL(url);
  };

  return (
    <View style={styles.container}>
      {status === 'idle' && (
        <Button title="Doğrulama Başlat" onPress={createSession} />
      )}

      {sessionId && status === 'loading' && (
        <View>
          <Text>Method Seçin:</Text>
          <Button title="📱 SMS" onPress={() => selectMethod('sms')} />
          <Button title="💬 WhatsApp" onPress={() => selectMethod('whatsapp')} />
        </View>
      )}

      {status === 'waiting' && verificationData && (
        <View style={styles.waitingContainer}>
          <Text style={styles.title}>Doğrulama Kodu Gönder</Text>
          
          <Image
            source={{ uri: verificationData.qrCodeData }}
            style={styles.qrCode}
          />
          
          <Text style={styles.label}>Bu numaraya mesaj gönderin:</Text>
          <Text style={styles.contact}>{verificationData.selectedServiceContact}</Text>
          
          <Text style={styles.label}>Mesaj içeriği:</Text>
          <Text style={styles.code}>{verificationData.verificationCode}</Text>
          
          {verificationData.method === 'sms' && (
            <Button title="SMS Uygulamasını Aç" onPress={openSMSApp} />
          )}
          {verificationData.method === 'whatsapp' && (
            <Button title="WhatsApp'ı Aç" onPress={openWhatsApp} />
          )}
          
          <Text style={styles.waiting}>Doğrulama bekleniyor...</Text>
        </View>
      )}

      {status === 'success' && (
        <View style={styles.successContainer}>
          <Text style={styles.successText}>✅ Doğrulama Başarılı!</Text>
        </View>
      )}

      {status === 'failed' && (
        <View style={styles.failedContainer}>
          <Text style={styles.failedText}>❌ Doğrulama Başarısız</Text>
          <Button title="Tekrar Dene" onPress={createSession} />
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20, justifyContent: 'center' },
  qrCode: { width: 200, height: 200, alignSelf: 'center', marginVertical: 20 },
  title: { fontSize: 24, fontWeight: 'bold', textAlign: 'center', marginBottom: 20 },
  label: { fontSize: 14, color: '#666', marginTop: 15 },
  contact: { fontSize: 20, fontWeight: 'bold', marginVertical: 5 },
  code: { fontSize: 32, fontWeight: 'bold', color: '#007AFF', marginVertical: 10 },
  waiting: { marginTop: 20, textAlign: 'center', color: '#666' },
  successText: { fontSize: 24, color: 'green', fontWeight: 'bold', textAlign: 'center' },
  failedText: { fontSize: 24, color: 'red', fontWeight: 'bold', textAlign: 'center' }
});

export default VerificationScreen;

Web Örneği (React + Socket.IO)

import React, { useState, useEffect } from 'react';
import io from 'socket.io-client';

function CustomVerificationUI({ apiKey, phone }) {
  const [sessionId, setSessionId] = useState(null);
  const [verificationData, setVerificationData] = useState(null);
  const [status, setStatus] = useState('idle');
  const [socket, setSocket] = useState(null);

  // Socket.IO bağlantısı
  useEffect(() => {
    const newSocket = io('https://www.verifly.net');
    setSocket(newSocket);

    return () => newSocket.close();
  }, []);

  // Socket event listener
  useEffect(() => {
    if (!socket || !sessionId) return;

    socket.emit('join-verification', sessionId);

    socket.on('verification-update', (data) => {
      if (data.status === 'verified') {
        setStatus('success');
        // Başarılı! Kullanıcıyı yönlendir
        setTimeout(() => {
          window.location.href = '/dashboard';
        }, 2000);
      } else if (data.status === 'failed') {
        setStatus('failed');
      }
    });

    return () => {
      socket.emit('leave-verification', sessionId);
      socket.off('verification-update');
    };
  }, [socket, sessionId]);

  const createSession = async () => {
    const response = await fetch('/api/verify/create', {
      method: 'POST',
      headers: {
        'X-API-Key': apiKey,
        'X-Signature': generateSignature(...),
        'X-Timestamp': Date.now(),
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        phone,
        methods: ['sms', 'whatsapp', 'call']
      })
    });

    const data = await response.json();
    setSessionId(data.data.sessionId);
  };

  const selectMethod = async (method) => {
    const response = await fetch(`/api/verify/${sessionId}/select-method`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ method })
    });

    const data = await response.json();
    setVerificationData(data.data);
    setStatus('waiting');
  };

  return (
    <div className="custom-verification">
      {!sessionId ? (
        <button onClick={createSession}>Doğrulama Başlat</button>
      ) : !verificationData ? (
        <div className="method-selection">
          <h3>Doğrulama Yöntemi Seçin</h3>
          <button onClick={() => selectMethod('sms')}>📱 SMS</button>
          <button onClick={() => selectMethod('whatsapp')}>💬 WhatsApp</button>
          <button onClick={() => selectMethod('call')}>📞 Sesli Arama</button>
        </div>
      ) : status === 'waiting' ? (
        <div className="verification-waiting">
          <h3>Doğrulama Kodu Gönder</h3>
          
          <img src={verificationData.qrCodeData} alt="QR Code" />
          
          <p><strong>{verificationData.selectedServiceContact}</strong> numarasına</p>
          <p className="code">{verificationData.verificationCode}</p>
          <p>kodunu gönderin</p>
          
          {verificationData.method === 'sms' && (
            <a href={`sms:${verificationData.selectedServiceContact}?body=${verificationData.verificationCode}`}>
              SMS Gönder
            </a>
          )}
          
          <div className="spinner">Doğrulama bekleniyor...</div>
        </div>
      ) : status === 'success' ? (
        <div className="success">
          <h2>✅ Doğrulama Başarılı!</h2>
          <p>Yönlendiriliyorsunuz...</p>
        </div>
      ) : (
        <div className="failed">
          <h2>❌ Doğrulama Başarısız</h2>
          <button onClick={createSession}>Tekrar Dene</button>
        </div>
      )}
    </div>
  );
}

Polling Yöntemi (Socket.IO Olmadan)

Socket.IO kullanamıyorsanız, GET /api/verify/:sessionId endpoint'ini düzenli aralıklarla çağırarak status kontrol edebilirsiniz.

// Polling fonksiyonu
const startStatusPolling = (sessionId, callback) => {
  const pollInterval = setInterval(async () => {
    try {
      const response = await fetch(`https://www.verifly.net/api/verify/${sessionId}`);
      const data = await response.json();
      
      if (data.success) {
        const status = data.data.status;
        
        // Callback'i çağır
        callback(status, data.data);
        
        // Terminal durumlar - polling'i durdur
        if (['verified', 'failed', 'expired'].includes(status)) {
          clearInterval(pollInterval);
        }
      }
    } catch (error) {
      console.error('Polling error:', error);
    }
  }, 2000); // Her 2 saniyede bir kontrol
  
  // Cleanup fonksiyonu döndür
  return () => clearInterval(pollInterval);
};

// Kullanım
const stopPolling = startStatusPolling(sessionId, (status, data) => {
  console.log('Status:', status);
  
  if (status === 'verified') {
    alert('✅ Doğrulama başarılı!');
    window.location.href = '/dashboard';
  } else if (status === 'failed') {
    alert('❌ Doğrulama başarısız!');
  } else if (status === 'expired') {
    alert('⏰ Oturum süresi doldu!');
  }
});

// Component unmount olduğunda durdur
// stopPolling();
⚠️ Polling Best Practices
  • Interval: 2-3 saniye ideal (çok sık istek yapmayın)
  • Timeout: Session süresi boyunca polling yapın (max 15 dakika)
  • Cleanup: Component unmount olduğunda interval'i temizleyin
  • Error Handling: Network hatalarında exponential backoff uygulayın
  • Battery: Mobil cihazlarda pil tüketimini göz önünde bulundurun

✅ Custom UI Hazır! Artık iframe'e ihtiyaç duymadan kendi doğrulama UI'ınızı oluşturabilirsiniz. Webhook'lar ile backend entegrasyonunu tamamlayabilirsiniz.

API Authentication

Verifly API'si, her isteği HMAC-SHA256 imzalama ile güvenli hale getirir. Bu yöntem, isteklerin değiştirilmesini engeller ve sadece sizin oluşturabileceğiniz imzaları doğrular.

🔐 Güvenlik Katmanları

  • ✓ Man-in-the-middle saldırılarını engeller
  • ✓ Request tampering'i tespit eder
  • ✓ Replay attack'ları önler (timestamp kontrolü)
  • ✓ Her istek için benzersiz imza gerektirir

Gerekli Header'lar

Her API isteğinde aşağıdaki header'lar gönderilmelidir:

Header Açıklama Zorunlu
X-API-Key Uygulamanızın public API key'i ✓ Evet
X-Signature HMAC-SHA256 imzası (hex formatında) ✓ Evet
X-Timestamp Unix timestamp (milisaniye cinsinden) ✓ Evet
Content-Type application/json ✓ Evet

İmza (Signature) Nasıl Oluşturulur?

📋 İmza Oluşturma Adımları

  1. 1. Şu anki zamanı milisaniye cinsinden alın: Date.now()
  2. 2. Request body'yi JSON string'e çevirin: JSON.stringify(payload)
  3. 3. Timestamp ve JSON'u birleştirin: timestamp + jsonString
  4. 4. Secret Key ile HMAC-SHA256 hash'i oluşturun
  5. 5. Hash'i hexadecimal formatına çevirin

Node.js Örnek Kod

const crypto = require('crypto');

function createSignature(payload, secretKey) {
  // 1. Timestamp al
  const timestamp = Date.now().toString();
  
  // 2. Payload'ı JSON string yap
  const jsonPayload = JSON.stringify(payload);
  
  // 3. Data string oluştur
  const data = timestamp + jsonPayload;
  
  // 4. HMAC-SHA256 hash oluştur
  const signature = crypto
    .createHmac('sha256', secretKey)
    .update(data)
    .digest('hex');
  
  return { signature, timestamp };
}

// Kullanım örneği
const payload = {
  phone: '05461234567',
  methods: ['sms', 'whatsapp'],
  lang: 'tr'
};

const { signature, timestamp } = createSignature(payload, 'sk_your_secret_key');

console.log('Timestamp:', timestamp);
console.log('Signature:', signature);

PHP Örnek Kod

<?php

function createSignature($payload, $secretKey) {
    // 1. Timestamp al (milisaniye)
    $timestamp = (string)(time() * 1000);
    
    // 2. Payload'ı JSON yap
    $jsonPayload = json_encode($payload);
    
    // 3. Data string oluştur
    $data = $timestamp . $jsonPayload;
    
    // 4. HMAC-SHA256 hash oluştur
    $signature = hash_hmac('sha256', $data, $secretKey);
    
    return [
        'signature' => $signature,
        'timestamp' => $timestamp
    ];
}

// Kullanım örneği
$payload = [
    'phone' => '05461234567',
    'methods' => ['sms', 'whatsapp'],
    'lang' => 'tr'
];

$result = createSignature($payload, 'your_secret_key');

echo 'Timestamp: ' . $result['timestamp'] . "\n";
echo 'Signature: ' . $result['signature'] . "\n";

?>

Python Örnek Kod

import hmac
import hashlib
import json
import time

def create_signature(payload, secret_key):
    # 1. Timestamp al (milisaniye)
    timestamp = str(int(time.time() * 1000))
    
    # 2. Payload'ı JSON yap
    json_payload = json.dumps(payload)
    
    # 3. Data string oluştur
    data = timestamp + json_payload
    
    # 4. HMAC-SHA256 hash oluştur
    signature = hmac.new(
        secret_key.encode('utf-8'),
        data.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return {
        'signature': signature,
        'timestamp': timestamp
    }

# Kullanım örneği
payload = {
    'phone': '05461234567',
    'methods': ['sms', 'whatsapp'],
    'lang': 'tr'
}

result = create_signature(payload, 'your_secret_key')

print(f'Timestamp: {result["timestamp"]}')
print(f'Signature: {result["signature"]}')

⚠️ Önemli Güvenlik Notları

  • Secret Key'i ASLA frontend kodunda kullanmayın
  • Secret Key'i environment variable olarak saklayın
  • Git repository'e commit etmeyin (.env dosyasını .gitignore'a ekleyin)
  • Timestamp 5 dakikadan eski istekler reddedilir (replay attack önlemi)
  • Her istek için yeni signature oluşturulmalıdır

Test Etme

İmza oluşturma fonksiyonunuzu test etmek için basit bir örnek:

Input

Secret Key:

test_secret_123

Payload:

{"phone":"05551234567"}

Timestamp:

1704067200000

Expected Output

Data String:

1704067200000{"phone":"05551234567"}

Signature (HMAC):

a1b2c3d4e5f6...

API Endpoints

Verifly API'si RESTful tasarım prensiplerini takip eder. Tüm istekler https://www.verifly.net/api base URL'i ile başlar.

📌 Genel Bilgiler

  • Base URL: https://www.verifly.net/api
  • Format: JSON (application/json)
  • Encoding: UTF-8
  • Rate Limit: 100 req/dakika (session oluşturma), 1000 req/dakika (diğer)
POST /verify/create

Yeni bir doğrulama session'ı oluşturur

Request Body

Parametre Tip Açıklama Zorunlu
phone string Telefon numarası (ör: "05461234567"). Verilmezse kullanıcı iframe'de girer. Hayır
email string E-posta adresi. Verilmezse kullanıcı iframe'de girer. Hayır
methods array İzin verilen yöntemler: ["sms", "whatsapp", "call", "email"] Hayır
lang string Dil kodu: "tr" veya "en" (varsayılan: "tr") Hayır
timeout number Session süresi (dakika): 1-15 arası (varsayılan: 2) Hayır
webhookUrl string Doğrulama sonucu gönderilecek webhook URL Hayır
redirectUrl string Başarılı doğrulama sonrası yönlendirilecek URL Hayır
data string Özel veri (JSON, JWT token veya herhangi bir string) - başarılı doğrulama sırasında webhook'ta geri gönderilir Hayır

💡 Not: phone veya email parametreleri opsiyoneldir. Eğer verilmezse, kullanıcı seçtiği yönteme göre iframe içinde bu bilgileri kendisi girer.

💡 Not: webhookUrl parametresi opsiyoneldir. Eğer girilmezse uygulama ayarlarındaki webhookURL'e post edilir.

Örnek Request

curl -X POST https://www.verifly.net/api/verify/create \
  -H "X-API-Key: vf_your_api_key" \
  -H "X-Signature: generated_signature" \
  -H "X-Timestamp: 1704067200000" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "05461234567",
    "methods": ["sms", "whatsapp"],
    "lang": "tr",
    "timeout": 5,
    "webhookUrl": "https://yoursite.com/webhook",
    "data": "user_123_or_jwt_token"
  }'

Response (200 OK)

{
  "success": true,
  "data": {
    "sessionId": "abc-123-def-456",
    "iframeUrl": "https://www.verifly.net/verify/iframe/abc-123",
    "expiresAt": "2024-01-01T12:30:00.000Z",
    "allowedMethods": ["sms", "whatsapp"],
    "method": null,
    "userInputRequired": false
  }
}

Response Field Açıklamaları

sessionId

Benzersiz session ID'si. Tüm işlemlerde kullanılır.

iframeUrl

Iframe içine gömülecek doğrulama sayfası URL'i.

allowedMethods

Kullanılabilir doğrulama yöntemleri.

userInputRequired

Kullanıcının telefon/email girmesi gerekip gerekmediği.

GET /verify/:sessionId

Session durumunu ve detaylarını sorgular

URL Parameters

sessionId - Session oluştururken aldığınız benzersiz ID

Örnek Request

curl -X GET https://www.verifly.net/api/verify/abc-123-def-456 \
  -H "X-API-Key: vf_your_api_key" \
  -H "X-Signature: generated_signature" \
  -H "X-Timestamp: 1704067200000"

Response (200 OK)

{
  "success": true,
  "data": {
    "sessionId": "abc-123-def-456",
    "status": "success",
    "method": "sms",
    "recipientContact": "05461234567",
    "selectedServiceContact": "08502411444",
    "verifiedAt": "2024-01-01T12:25:30.000Z",
    "expiresAt": "2024-01-01T12:30:00.000Z",
    "createdAt": "2024-01-01T12:20:00.000Z"
  }
}

Status Değerleri

Status Açıklama
pending Kullanıcı henüz yöntem seçmedi
waiting Yöntem seçildi, doğrulama kodu bekleniyor
success Doğrulama başarıyla tamamlandı
expired Session süresi doldu
cancelled Kullanıcı iptal etti
POST /verify/:sessionId/select-method

Doğrulama yöntemi seçer ve doğrulama sürecini başlatır (Custom UI için)

URL Parameters

sessionId - Session ID

Request Body

Parametre Tip Açıklama Zorunlu
method string Seçilen yöntem: "sms", "whatsapp", "call", "email" ✓ Evet
recipientContact string Telefon/email (create'de verilmemişse gerekli) Hayır*

Örnek Request

curl -X POST https://www.verifly.net/api/verify/abc-123/select-method \
  -H "Content-Type: application/json" \
  -d '{
    "method": "sms"
  }'

Response (200 OK)

{
  "success": true,
  "data": {
    "sessionId": "abc-123-def-456",
    "status": "waiting",
    "method": "sms",
    "recipientContact": "05461234567",
    "selectedServiceContact": "08502411444",
    "verificationCode": "123456",
    "qrCodeData": "data:image/png;base64,iVBORw0KGgo...",
    "expiresAt": "2024-01-01T12:30:00.000Z"
  }
}

💡 Önemli: Bu endpoint Custom UI kullanırken kritiktir. Response'da verificationCode ve selectedServiceContact döner, bunları kullanıcıya göstererek doğrulama kodunu göndermesini istersiniz.

Response Field Açıklamaları

verificationCode

Kullanıcının göndermesi gereken 6 haneli kod

selectedServiceContact

Kodun gönderileceği telefon/email adresi

qrCodeData

QR kod PNG verisi (base64), tarayarak hızlı gönderim için

status

"waiting" - Doğrulama kodu bekleniyor

POST /verify/:sessionId/cancel

Mevcut doğrulama yöntemini iptal eder, kullanıcı başka yöntem seçebilir

URL Parameters

sessionId - Session ID

💡 Kullanım: Session açık kalır, sadece seçili yöntem sıfırlanır. Kullanıcı başka bir yöntem deneyebilir. Tamamen iptal için /abort kullanın.

Örnek Request

curl -X POST https://www.verifly.net/api/verify/abc-123/cancel \
  -H "Content-Type: application/json"

Response (200 OK)

{
  "success": true,
  "message": "Verification reset - select another method",
  "data": {
    "sessionId": "abc-123-def-456",
    "status": "pending"
  }
}
POST /verify/:sessionId/abort

Session'ı tamamen iptal eder ve kapatır (geri dönüş yok)

URL Parameters

sessionId - Session ID

⚠️ Dikkat: Bu işlem geri alınamaz! Session status'ü "failed" olarak işaretlenir ve webhook gönderilir. Yeni doğrulama için yeni session oluşturmalısınız.

Örnek Request

curl -X POST https://www.verifly.net/api/verify/abc-123/abort \
  -H "Content-Type: application/json"

Response (200 OK)

{
  "success": true,
  "message": "Verification aborted successfully",
  "data": {
    "sessionId": "abc-123-def-456",
    "status": "aborted",
    "abortedAt": "2024-01-01T12:25:00.000Z"
  }
}
GET /verify/balance

Hesap bakiyenizi sorgular

Örnek Request

curl -X GET https://www.verifly.net/api/verify/balance \
  -H "X-API-Key: vf_your_api_key" \
  -H "X-Signature: generated_signature" \
  -H "X-Timestamp: 1704067200000"

Response (200 OK)

{
  "success": true,
  "data": {
    "balance": 150.50,
    "currency": "TRY",
    "userId": "507f1f77bcf86cd799439011"
  }
}

⚠️ Hata Kodları

400 Bad Request

Geçersiz parametre veya eksik alan

401 Unauthorized

Geçersiz API Key veya Signature

404 Not Found

Session bulunamadı

429 Too Many Requests

Rate limit aşıldı

402 Payment Required

Yetersiz bakiye

Webhooks

Webhook'lar, doğrulama işlemi tamamlandığında otomatik olarak sunucunuza POST isteği gönderir. Bu sayede gerçek zamanlı bildirim alabilir ve kullanıcı işlemlerini otomatikleştirebilirsiniz.

✨ Webhook Avantajları

Gerçek zamanlı bildirim
Polling'e gerek yok
Güvenli HMAC doğrulama
Otomatik retry mekanizması

Webhook URL Ayarlama

Webhook URL'ini iki şekilde ayarlayabilirsiniz:

1️⃣ Uygulama Seviyesinde

Dashboard'dan her uygulama için varsayılan webhook URL ayarlayın.

Settings → Webhook URL

2️⃣ Session Bazında

Session oluştururken webhookUrl parametresi ile özel URL belirleyin.

POST /verify/create

Webhook Payload

Doğrulama tamamlandığında webhook URL'inize aşağıdaki formatta POST isteği gönderilir:

Webhook Headers

  • Content-Type: application/json
  • X-Verifly-Signature: hmac_sha256_hash
  • X-Verifly-Timestamp: unix_timestamp_ms
  • X-Verifly-Attempt: attempt_number
  • User-Agent: Verifly-Webhook/1.0

Webhook Body

{
  "event": "verification.success",
  "sessionId": "abc-123-def-456",
  "status": "success",
  "data": "custom_data_or_jwt_token_or_json_string",
  "method": "sms",
  "recipientContact": "05461234567",
  "verifiedAt": "2024-01-01T12:25:30.000Z",
  "createdAt": "2024-01-01T12:20:00.000Z",
  "attemptCount": 1
}

Event Tipleri

Event Açıklama
verification.success Doğrulama başarıyla tamamlandı.
verification.failed Doğrulama başarısız.
verification.expired Doğrulama için izin verilen süre doldu.
verification.cancelled Yeni doğrulama başlatıldı ve mevcut doğrulama iptal edildi.
verification.aborted Kullanıcı doğrulamayı iptal etti.

Webhook Güvenliği (HMAC Doğrulama)

Her webhook isteği X-Verifly-Signature header'ı ile imzalanır. Bu imzayı doğrulayarak isteğin gerçekten Verifly'dan geldiğinden emin olabilirsiniz.

🔒 İmza Doğrulama Adımları

  1. 1. Header'lardan X-Verifly-Signature ve X-Verifly-Timestamp'i alın
  2. 2. Timestamp'in 5 dakikadan eski olmadığını kontrol edin (replay attack önlemi)
  3. 3. Webhook payload'ını JSON string'e çevirin
  4. 4. Data string oluşturun: timestamp + jsonPayload
  5. 5. Secret Key ile HMAC-SHA256 hash'i oluşturun
  6. 6. Oluşturduğunuz hash ile gelen X-Verifly-SignaturetimingSafeEqual ile karşılaştırın
  7. 7. Eşleşmiyorsa isteği reddedin (401 döndürün)

Node.js Webhook Handler

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const SECRET_KEY = process.env.VERIFLY_SECRET_KEY;

// Webhook endpoint
app.post('/webhook/verifly', (req, res) => {
  // 1. Header'lardan bilgileri al
  const signature = req.headers['x-verifly-signature'];
  const timestamp = req.headers['x-verifly-timestamp'];
  
  if (!signature || !timestamp) {
    return res.status(401).json({ error: 'Missing signature or timestamp' });
  }
  
  // 2. Timestamp kontrolü (5 dakika)
  const now = Date.now();
  if (Math.abs(now - parseInt(timestamp)) > 5 * 60 * 1000) {
    return res.status(401).json({ error: 'Timestamp too old' });
  }
  
  // 3. Payload'ı JSON string yap
  const payload = JSON.stringify(req.body);
  const data = timestamp + payload;
  
  // 4. Kendi signature'ımızı oluştur
  const expectedSignature = crypto
    .createHmac('sha256', SECRET_KEY)
    .update(data)
    .digest('hex');
  
  // 5. Signature'ları güvenli karşılaştır
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
    console.log('⚠️ Invalid webhook signature!');
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // 6. Event tipine göre işlem yap
  const { event, sessionId, status, data } = req.body;
  
  if (event === 'verification.success') {
    console.log('✅ Verification successful:', sessionId);
    console.log('Method:', data.method);
    console.log('Contact:', data.recipientContact);
    
    // Veritabanını güncelle, kullanıcıyı aktif et, vs.
    // updateUserVerification(sessionId, data);
  } else if (event === 'verification.failed') {
    console.log('❌ Verification failed:', sessionId);
  } else if (event === 'verification.expired') {
    console.log('⏰ Verification expired:', sessionId);
  } else if (event === 'verification.cancelled') {
    console.log('🚫 Verification cancelled:', sessionId);
  }
  
  // 7. 200 OK döndür
  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

PHP Webhook Handler

<?php
// webhook.php

$secretKey = getenv('VERIFLY_SECRET_KEY');

// 1. Header'lardan bilgileri al
$signature = $_SERVER['HTTP_X_VERIFLY_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_VERIFLY_TIMESTAMP'] ?? '';

if (empty($signature) || empty($timestamp)) {
    http_response_code(401);
    echo json_encode(['error' => 'Missing signature or timestamp']);
    exit;
}

// 2. Timestamp kontrolü (5 dakika)
$now = time() * 1000; // milisaniye
if (abs($now - intval($timestamp)) > 5 * 60 * 1000) {
    http_response_code(401);
    echo json_encode(['error' => 'Timestamp too old']);
    exit;
}

// 3. Payload'ı al
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);

// 4. Signature data oluştur (timestamp + payload)
$signatureData = $timestamp . $payload;

// 5. Kendi signature'ımızı oluştur
$expectedSignature = hash_hmac('sha256', $signatureData, $secretKey);

// 6. Signature'ları güvenli karşılaştır
if (!hash_equals($expectedSignature, $signature)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// 7. Event tipine göre işlem yap
$event = $data['event'];
$sessionId = $data['sessionId'];
$status = $data['status'];

switch ($event) {
    case 'verification.success':
        error_log("✅ Verification successful: $sessionId");
        // Veritabanını güncelle
        // updateUserVerification($sessionId, $data['data']);
        break;
    
    case 'verification.failed':
        error_log("❌ Verification failed: $sessionId");
        break;
    
    case 'verification.expired':
        error_log("⏰ Verification expired: $sessionId");
        break;
    
    case 'verification.cancelled':
        error_log("🚫 Verification cancelled: $sessionId");
        break;
}

// 8. 200 OK döndür
http_response_code(200);
echo json_encode(['received' => true]);
?>

Retry Mekanizması

Webhook isteği başarısız olursa (timeout, 5xx hata, vs.) Verifly otomatik olarak tekrar dener:

1. Deneme

Hemen

2. Deneme

30 saniye sonra

3. Deneme

5 dakika sonra

💡 Best Practices

  • Webhook endpoint'iniz 200 OK dönmeli (5 saniye içinde)
  • Ağır işlemleri queue'ya atın, webhook'u hızlı cevaplayın
  • Her webhook için idempotent işlem yapın (aynı istek 2 kez gelirse sorun çıkmasın)
  • HTTPS kullanın (HTTP webhook URL'leri reddedilir)
  • Signature doğrulamayı atlamayın (güvenlik açığı yaratır)

Test Etme

Webhook'larınızı test etmek için geliştirme ortamında şu araçları kullanabilirsiniz:

🌐 ngrok

Local sunucunuzu public URL'e çevirir

ngrok http 3000

🪝 webhook.site

Gelen webhook'ları görselleştirir

webhook.site

Şifresiz Giriş (Passwordless Login)

Verifly OAuth ile kullanıcılarınızın şifre girmeden uygulamalarınıza giriş yapmalarını sağlayabilirsiniz.

✨ Avantajları

Şifre gerektirmez, daha güvenli
Tek tıkla giriş, kolay entegrasyon
E-posta, SMS, WhatsApp, Arama desteği
Hazır JavaScript SDK

Genel Bakış

Verifly OAuth, kullanıcılarınızın e-posta veya telefon numarası ile şifre kullanmadan giriş yapmalarını sağlayan bir sistemdir. Tek bir JavaScript SDK ile entegre edebilirsiniz.

Giriş Akışı

OAuth akışı 4 adımdan oluşur:

1
Kullanıcı Butona Tıklar

SDK ile oluşturulmuş 'Sign in with Verifly' butonuna tıklar

2
Initiate Endpoint'e Yönlendirilir

Backend'inizdeki /oauth/verifly/initiate endpoint'ine yönlendirilir, signature oluşturulur

3
Verifly OAuth Sayfasına Redirect

www.verifly.net/oauth sayfasına redirect edilir, kullanıcı e-posta/telefon girer ve doğrulama yapar

4
Callback ve Session Oluşturma

Doğrulama başarılı olursa callback URL'e yönlendirilir, backend session'ı doğrular ve kullanıcı giriş yapar

1. SDK Kurulumu

Verifly OAuth SDK'sını sayfanıza ekleyerek başlayın.

SDK'yı Ekleyin

<!-- HEAD bölümüne ekleyin -->
<script src="https://www.verifly.net/js/verifly-oauth.js"></script>

SDK'yı Kullanın

<!-- Buton için container oluşturun -->
<div id="verifly-signin-btn"></div>

<script>
  VeriflyOAuth.init({
    initiateUrl: '/oauth/verifly',
    siteName: 'Your Site Name',
    buttonId: 'verifly-signin-btn',
    buttonText: 'Sign in with Verifly',
    buttonStyle: 'default', // default, light, dark
  });
</script>

SDK Ayarları

Parametre Tip Açıklama
initiateUrl required Backend'inizdeki initiate endpoint URL'i
siteName required OAuth sayfasında gösterilecek site adı
buttonId required Butonun oluşturulacağı div'in ID'si
buttonText optional Buton üzerindeki metin. Varsayılan: 'Sign in with Verifly'
buttonStyle optional Buton stili: 'default', 'light', 'dark'. Varsayılan: 'default'

2. Initiate Endpoint

Kullanıcı butona tıkladığında SDK bu endpoint'i çağırır. Burada signature oluşturup Verifly OAuth sayfasına redirect yapmalısınız.

// Örnek Initiate Endpoint
router.get('/oauth/verifly', async (req, res) => {
  try {
    const crypto = require('crypto');

    // Environment'tan credentials al
    const API_KEY = process.env.VERIFLY_API_KEY;
    const SECRET_KEY = process.env.VERIFLY_SECRET_KEY;

    if (!API_KEY || !SECRET_KEY) {
      return res.status(500).send('Credentials not configured');
    }

    // Signature oluştur
    const timestamp = Date.now();
    const signature = crypto
      .createHmac('sha256', SECRET_KEY)
      .update(`${timestamp}`)
      .digest('hex');

    // Callback URL belirle
    const baseUrl = req.protocol + '://' + req.get('host');
    const callbackUrl = `${baseUrl}/oauth/verifly/callback`;

    // Query parametrelerini al
    const theme = req.query.theme || 'default';
    const site = req.query.site || 'Verifly';
    const methods = req.query.methods || '';

    // OAuth URL'ini oluştur
    let oauthUrl = `https://www.verifly.net/oauth?client_key=${encodeURIComponent(API_KEY)}×tamp=${timestamp}&signature=${encodeURIComponent(signature)}&callback_url=${encodeURIComponent(callbackUrl)}&site=${encodeURIComponent(site)}&theme=${theme}`;
    
    if (methods) {
      oauthUrl += `&methods=${encodeURIComponent(methods)}`;
    }

    // Verifly OAuth'a redirect yap
    res.redirect(oauthUrl);
  } catch (error) {
    console.error('OAuth initiate error:', error);
    res.status(500).send('Initiate error: ' + error.message);
  }
});

OAuth URL Parametreleri

Parametre Açıklama
client_key API Key
timestamp Timestamp (milisaniye)
signature HMAC-SHA256 signature (timestamp ile hesaplanır)
callback_url Doğrulama başarılı olduğunda yönlendirilecek URL
site OAuth sayfasında gösterilecek site adı
theme Tema: 'default', 'light', 'dark'
methods İzin verilen doğrulama metodları (virgülle ayrılmış): 'email,sms,whatsapp,call'

3. Callback Endpoint

Kullanıcı doğrulamayı tamamladıktan sonra Verifly bu endpoint'e yönlendirir. Burada session'ı doğrulayıp kullanıcı girişini tamamlamalısınız.

// Örnek Callback Endpoint
router.get('/oauth/verifly/callback', async (req, res) => {
  try {
    const { session_id } = req.query;

    // session_id parametresini kontrol et
    if (!session_id) {
      return res.status(400).send('Missing session_id');
    }

    const crypto = require('crypto');
    const axios = require('axios');

    const API_KEY = process.env.VERIFLY_API_KEY;
    const SECRET_KEY = process.env.VERIFLY_SECRET_KEY;

    // Verify için signature oluştur
    const timestamp = Date.now();
    const signatureData = `${timestamp}${session_id}`;
    const signature = crypto
      .createHmac('sha256', SECRET_KEY)
      .update(signatureData)
      .digest('hex');

    // Verifly API'sine verify isteği gönder
    const verifyResponse = await axios.post(`https://www.verifly.net/api/oauth/${session_id}/verify`,
      {},
      {
        headers: {
          'X-API-Key': API_KEY,
          'X-Signature': signature,
          'X-Timestamp': timestamp.toString(),
          'Content-Type': 'application/json',
        },
      }
    );

    const sessionData = verifyResponse.data.data;

    // Dönen veri
    // sessionData = {
    //   status: 'verified',
    //   method: 'email',
    //   email: '[email protected]',
    //   phone: null,
    //   recipientContact: '[email protected]',
    //   sessionId: 'abc-123',
    //   verifiedAt: '2024-01-01T12:00:00.000Z'
    // }

    // Kullanıcıyı veritabanında ara
    let user = await User.findOne({
      $or: [
        { email: sessionData.email },
        { phone: sessionData.phone }
      ]
    });

    if (!user) {
      // Yoksa yeni kullanıcı oluştur
      user = await User.create({
        email: sessionData.email,
        phone: sessionData.phone,
        verified: true,
        verifiedAt: sessionData.verifiedAt
      });
    }

    // Session'a kaydet
    req.session.userId = user._id;
    req.session.email = sessionData.email;

    // Dashboard'a yönlendir
    res.redirect('/dashboard');
  } catch (error) {
    console.error('OAuth callback error:', error);
    res.status(500).send('Callback error: ' + error.message);
  }
});

Verify API Response

{
  "success": true,
  "data": {
    "status": "verified",
    "method": "email",
    "email": "[email protected]",
    "phone": null,
    "recipientContact": "[email protected]",
    "sessionId": "abc-123-def-456",
    "verifiedAt": "2024-01-01T12:00:00.000Z"
  }
}

4. Tam Entegrasyon Örneği

Frontend (HTML)

<!DOCTYPE html>
<html>
<head>
  <title>Login with Verifly</title>
  <script src="https://www.verifly.net/js/verifly-oauth.js"></script>
</head>
<body>
  <h1>Welcome</h1>
  
  <!-- SDK butonun oluşturulacağı container -->
  <div id="verifly-signin-btn"></div>

  <script>
    VeriflyOAuth.init({
      initiateUrl: '/oauth/verifly',
      siteName: 'My Awesome Site',
      buttonId: 'verifly-signin-btn',
      buttonText: 'Sign in with Verifly',
      buttonStyle: 'default'
    });
  </script>
</body>
</html>

Backend (Node.js/Express)

const express = require('express');
const crypto = require('crypto');
const axios = require('axios');

const router = express.Router();

// Environment variables
const API_KEY = process.env.VERIFLY_API_KEY;
const SECRET_KEY = process.env.VERIFLY_SECRET_KEY;

// Initiate route
router.get('/oauth/verifly', async (req, res) => {
  const timestamp = Date.now();
  const signature = crypto
    .createHmac('sha256', SECRET_KEY)
    .update(`${timestamp}`)
    .digest('hex');

  const baseUrl = req.protocol + '://' + req.get('host');
  const callbackUrl = `${baseUrl}/oauth/verifly/callback`;
  const theme = req.query.theme || 'default';
  const site = req.query.site || 'My Site';

  const oauthUrl = `https://www.verifly.net/oauth?client_key=${API_KEY}×tamp=${timestamp}&signature=${signature}&callback_url=${encodeURIComponent(callbackUrl)}&site=${site}&theme=${theme}`;

  res.redirect(oauthUrl);
});

// Callback route
router.get('/oauth/verifly/callback', async (req, res) => {
  const { session_id } = req.query;
  
  const timestamp = Date.now();
  const signatureData = `${timestamp}${session_id}`;
  const signature = crypto
    .createHmac('sha256', SECRET_KEY)
    .update(signatureData)
    .digest('hex');

  const baseUrl = `${req.protocol}://${req.get('host')}`;
  const verifyResponse = await axios.post(
    `${baseUrl}/api/oauth/${session_id}/verify`,
    {},
    {
      headers: {
        'X-API-Key': API_KEY,
        'X-Signature': signature,
        'X-Timestamp': timestamp.toString()
      }
    }
  );

  const { email, phone } = verifyResponse.data.data;

  // Kullanıcıyı giriş yap
  req.session.userEmail = email;
  req.session.userPhone = phone;

  res.redirect('/dashboard');
});

module.exports = router;

💡 En İyi Uygulamalar

  • ✓ API Key ve Secret Key'i environment variables'da saklayın
  • ✓ Secret key'i asla frontend'e göndermeyin
  • ✓ Callback endpoint'te mutlaka signature doğrulaması yapın
  • ✓ Session verilerini güvenli bir şekilde saklayın
  • ✓ HTTPS kullanın ve callback URL'leri doğru yapılandırın

Kod Örnekleri

Farklı programlama dilleri ve framework'ler için tam çalışan entegrasyon örnekleri.

Node.js / Express - Tam Entegrasyon

// server.js
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const API_KEY = 'vf_your_api_key';
const SECRET_KEY = 'your_secret_key';

// Signature oluştur
function createSignature(payload) {
  const timestamp = Date.now().toString();
  const data = timestamp + JSON.stringify(payload);
  const signature = crypto.createHmac('sha256', SECRET_KEY).update(data).digest('hex');
  return { signature, timestamp };
}

// Session oluştur
app.post('/api/verify', async (req, res) => {
  const payload = {
    phone: req.body.phone,
    methods: ['sms', 'whatsapp'],
    lang: 'tr',
    webhookUrl: 'https://yoursite.com/webhook'
  };
  
  const { signature, timestamp } = createSignature(payload);
  
  const response = await axios.post('https://www.verifly.net/api/verify/create', payload, {
    headers: {
      'X-API-Key': API_KEY,
      'X-Signature': signature,
      'X-Timestamp': timestamp
    }
  });
  
  res.json(response.data);
});

// Webhook handler
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-verifly-signature'];
  const timestamp = req.headers['x-verifly-timestamp'];
  
  // Signature doğrula
  const data = timestamp + JSON.stringify(req.body);
  const expected = crypto.createHmac('sha256', SECRET_KEY).update(data).digest('hex');
  
  if (signature !== expected) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Event işle
  if (req.body.event === 'verification.success') {
    console.log('✅ Verified:', req.body.sessionId);
  }
  
  res.json({ received: true });
});

app.listen(3000);

PHP / Laravel - Service + Controller

<?php
// app/Services/VeriflyService.php
namespace App\Services;

use Illuminate\Support\Facades\Http;

class VeriflyService {
    private $apiKey;
    private $secretKey;
    
    public function __construct() {
        $this->apiKey = config('services.verifly.api_key');
        $this->secretKey = config('services.verifly.secret_key');
    }
    
    public function createSession($phone = null) {
        $payload = [
            'phone' => $phone,
            'methods' => ['sms', 'whatsapp'],
            'lang' => 'tr',
            'webhookUrl' => route('webhook.verifly')
        ];
        
        $timestamp = (string)(time() * 1000);
        $data = $timestamp . json_encode($payload);
        $signature = hash_hmac('sha256', $data, $this->secretKey);
        
        return Http::withHeaders([
            'X-API-Key' => $this->apiKey,
            'X-Signature' => $signature,
            'X-Timestamp' => $timestamp
        ])->post('https://www.verifly.net/api/verify/create', $payload)->json();
    }
    
    public function verifyWebhook($signature, $timestamp, $payload) {
        $data = $timestamp . json_encode($payload);
        $expected = hash_hmac('sha256', $data, $this->secretKey);
        return hash_equals($expected, $signature);
    }
}

// app/Http/Controllers/WebhookController.php
namespace App\Http\Controllers;

use App\Services\VeriflyService;

class WebhookController extends Controller {
    public function handle(Request $request, VeriflyService $verifly) {
        $sig = $request->header('X-Verifly-Signature');
        $ts = $request->header('X-Verifly-Timestamp');
        
        if (!$verifly->verifyWebhook($sig, $ts, $request->all())) {
            return response()->json(['error' => 'Invalid'], 401);
        }
        
        if ($request->event === 'verification.success') {
            // Kullanıcıyı doğrulanmış işaretle
        }
        
        return response()->json(['received' => true]);
    }
}
?>

Python / Flask - Basit Entegrasyon

from flask import Flask, request, jsonify
import requests
import hmac
import hashlib
import json
import time

app = Flask(__name__)

API_KEY = 'vf_your_api_key'
SECRET_KEY = 'your_secret_key'

def create_signature(payload):
    timestamp = str(int(time.time() * 1000))
    data = timestamp + json.dumps(payload, separators=(',', ':'))
    signature = hmac.new(
        SECRET_KEY.encode(),
        data.encode(),
        hashlib.sha256
    ).hexdigest()
    return {'signature': signature, 'timestamp': timestamp}

@app.route('/api/verify', methods=['POST'])
def create_session():
    payload = {
        'phone': request.json.get('phone'),
        'methods': ['sms', 'whatsapp'],
        'lang': 'tr',
        'webhookUrl': 'https://yoursite.com/webhook'
    }
    
    auth = create_signature(payload)
    
    response = requests.post(
        'https://www.verifly.net/api/verify/create',
        json=payload,
        headers={
            'X-API-Key': API_KEY,
            'X-Signature': auth['signature'],
            'X-Timestamp': auth['timestamp']
        }
    )
    
    return jsonify(response.json())

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Verifly-Signature')
    timestamp = request.headers.get('X-Verifly-Timestamp')
    
    data = timestamp + json.dumps(request.json, separators=(',', ':'))
    expected = hmac.new(SECRET_KEY.encode(), data.encode(), hashlib.sha256).hexdigest()
    
    if signature != expected:
        return jsonify({'error': 'Invalid'}), 401
    
    if request.json['event'] == 'verification.success':
        print('✅ Verified:', request.json['sessionId'])
    
    return jsonify({'received': True})

app.run(port=5000)

React Native - Iframe ile WebView Entegrasyonu

💡 Önerilen Yaklaşım: React Native'de iframe URL'ini WebView ile gösterin. Custom UI için API Endpoints bölümündeki detayları inceleyin.

// VeriflyService.js
import CryptoJS from 'crypto-js';

const API_KEY = 'vf_your_api_key';
const SECRET_KEY = 'your_secret_key';
const BASE_URL = 'https://www.verifly.net/api';

function createSignature(payload) {
  const timestamp = Date.now().toString();
  const data = timestamp + JSON.stringify(payload);
  const signature = CryptoJS.HmacSHA256(data, SECRET_KEY).toString();
  return { signature, timestamp };
}

export async function createVerification(phone) {
  const payload = {
    phone,
    methods: ['sms', 'whatsapp'],
    lang: 'tr',
    webhookUrl: 'https://yourapi.com/webhook'
  };
  
  const { signature, timestamp } = createSignature(payload);
  
  const response = await fetch(`${BASE_URL}/verify/create`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
      'X-Signature': signature,
      'X-Timestamp': timestamp
    },
    body: JSON.stringify(payload)
  });
  
  return await response.json();
}

// VerificationScreen.js
import React, { useState, useRef } from 'react';
import { View, StyleSheet } from 'react-native';
import { WebView } from 'react-native-webview';
import { createVerification } from './VeriflyService';

export default function VerificationScreen({ userPhone }) {
  const [iframeUrl, setIframeUrl] = useState(null);
  const webViewRef = useRef(null);

  React.useEffect(() => {
    // Session oluştur ve iframe URL al
    createVerification(userPhone).then(result => {
      setIframeUrl(result.data.iframeUrl);
    });
  }, [userPhone]);

  const handleWebViewMessage = (event) => {
    const data = JSON.parse(event.nativeEvent.data);
    
    if (data.event === 'verification.success') {
      console.log('✅ Doğrulama başarılı!', data.sessionId);
      // Backend'e bildir veya navigation yap
    }
  };

  if (!iframeUrl) {
    return <View style={styles.loading} />;
  }

  return (
    <View style={styles.container}>
      <WebView
        ref={webViewRef}
        source={{ uri: iframeUrl }}
        style={styles.webview}
        onMessage={handleWebViewMessage}
        javaScriptEnabled={true}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  webview: {
    flex: 1,
  },
  loading: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
});

📦 Gerekli Paketler:

npm install react-native-webview crypto-js

Swift (iOS) - WKWebView ile Iframe Entegrasyonu

💡 Önerilen Yaklaşım: WKWebView ile iframe URL'ini gösterin. Custom UI için API Endpoints bölümündeki detayları inceleyin.

// VeriflyService.swift
import Foundation
import CryptoKit

class VeriflyService {
    let apiKey = "vf_your_api_key"
    let secretKey = "your_secret_key"
    let baseURL = "https://www.verifly.net/api"
    
    func createSignature(payload: [String: Any]) -> (signature: String, timestamp: String) {
        let timestamp = String(Int(Date().timeIntervalSince1970 * 1000))
        let jsonData = try! JSONSerialization.data(withJSONObject: payload)
        let jsonString = String(data: jsonData, encoding: .utf8)!
        let data = timestamp + jsonString
        
        let key = SymmetricKey(data: Data(secretKey.utf8))
        let signature = HMAC<SHA256>.authenticationCode(for: Data(data.utf8), using: key)
        let signatureHex = signature.map { String(format: "%02x", $0) }.joined()
        
        return (signatureHex, timestamp)
    }
    
    func createVerification(phone: String, completion: @escaping (Result<String, Error>) -> Void) {
        let payload: [String: Any] = [
            "phone": phone,
            "methods": ["sms", "whatsapp"],
            "lang": "tr",
            "webhookUrl": "https://yourapi.com/webhook"
        ]
        
        let (signature, timestamp) = createSignature(payload: payload)
        
        var request = URLRequest(url: URL(string: "\\(baseURL)/verify/create")!)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(apiKey, forHTTPHeaderField: "X-API-Key")
        request.setValue(signature, forHTTPHeaderField: "X-Signature")
        request.setValue(timestamp, forHTTPHeaderField: "X-Timestamp")
        request.httpBody = try? JSONSerialization.data(withJSONObject: payload)
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            if let data = data,
               let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
               let sessionData = json["data"] as? [String: Any],
               let iframeUrl = sessionData["iframeUrl"] as? String {
                completion(.success(iframeUrl))
            }
        }.resume()
    }
}

// VerificationViewController.swift
import UIKit
import WebKit

class VerificationViewController: UIViewController, WKScriptMessageHandler {
    var webView: WKWebView!
    let veriflyService = VeriflyService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // WKWebView setup
        let configuration = WKWebViewConfiguration()
        configuration.userContentController.add(self, name: "verifly")
        
        webView = WKWebView(frame: view.bounds, configuration: configuration)
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(webView)
        
        // Session oluştur ve iframe yükle
        loadVerification()
    }
    
    func loadVerification() {
        veriflyService.createVerification(phone: "05551234567") { result in
            switch result {
            case .success(let iframeUrl):
                DispatchQueue.main.async {
                    if let url = URL(string: iframeUrl) {
                        self.webView.load(URLRequest(url: url))
                    }
                }
            case .failure(let error):
                print("Error:", error)
            }
        }
    }
    
    // WebView message handler
    func userContentController(_ userContentController: WKUserContentController, 
                                didReceive message: WKScriptMessage) {
        if message.name == "verifly",
           let dict = message.body as? [String: Any],
           let event = dict["event"] as? String {
            
            if event == "verification.success" {
                print("✅ Doğrulama başarılı!")
                // Backend'e bildir veya navigation yap
            }
        }
    }
    
    deinit {
        webView?.configuration.userContentController.removeScriptMessageHandler(forName: "verifly")
    }
}

Kotlin (Android) - WebView ile Iframe Entegrasyonu

💡 Önerilen Yaklaşım: WebView ile iframe URL'ini gösterin. Custom UI için API Endpoints bölümündeki detayları inceleyin.

// VeriflyService.kt
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

class VeriflyService {
    private val apiKey = "vf_your_api_key"
    private val secretKey = "your_secret_key"
    private val baseURL = "https://www.verifly.net/api"
    private val client = OkHttpClient()
    
    private fun createSignature(payload: JSONObject): Pair<String, String> {
        val timestamp = System.currentTimeMillis().toString()
        val data = timestamp + payload.toString()
        
        val mac = Mac.getInstance("HmacSHA256")
        mac.init(SecretKeySpec(secretKey.toByteArray(), "HmacSHA256"))
        val signature = mac.doFinal(data.toByteArray()).joinToString("") { "%02x".format(it) }
        
        return Pair(signature, timestamp)
    }
    
    fun createVerification(phone: String, callback: (String?) -> Unit) {
        val payload = JSONObject().apply {
            put("phone", phone)
            put("methods", listOf("sms", "whatsapp"))
            put("lang", "tr")
            put("webhookUrl", "https://yourapi.com/webhook")
        }
        
        val (signature, timestamp) = createSignature(payload)
        
        val request = Request.Builder()
            .url("$baseURL/verify/create")
            .post(payload.toString().toRequestBody("application/json".toMediaType()))
            .addHeader("X-API-Key", apiKey)
            .addHeader("X-Signature", signature)
            .addHeader("X-Timestamp", timestamp)
            .build()
        
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                callback(null)
            }
            
            override fun onResponse(call: Call, response: Response) {
                val json = response.body?.string()?.let { JSONObject(it) }
                val iframeUrl = json?.getJSONObject("data")?.getString("iframeUrl")
                callback(iframeUrl)
            }
        })
    }
}

// VerificationActivity.kt
import android.annotation.SuppressLint
import android.os.Bundle
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.JavascriptInterface
import androidx.appcompat.app.AppCompatActivity

class VerificationActivity : AppCompatActivity() {
    private val veriflyService = VeriflyService()
    private lateinit var webView: WebView
    
    @SuppressLint("SetJavaScriptEnabled")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        webView = WebView(this)
        webView.settings.javaScriptEnabled = true
        webView.settings.domStorageEnabled = true
        webView.webViewClient = WebViewClient()
        webView.addJavascriptInterface(WebAppInterface(), "Android")
        
        setContentView(webView)
        
        // Session oluştur ve iframe yükle
        loadVerification()
    }
    
    private fun loadVerification() {
        veriflyService.createVerification("05551234567") { iframeUrl ->
            runOnUiThread {
                iframeUrl?.let {
                    webView.loadUrl(it)
                }
            }
        }
    }
    
    inner class WebAppInterface {
        @JavascriptInterface
        fun onVerificationSuccess(sessionId: String) {
            runOnUiThread {
                println("✅ Doğrulama başarılı: $sessionId")
                // Backend'e bildir veya navigation yap
            }
        }
    }
}

📦 Gradle Dependencies:

implementation 'com.squareup.okhttp3:okhttp:4.11.0'

image/svg+xml Go - Web Server ile Iframe Entegrasyonu

💡 Önerilen Yaklaşım: Backend'de session oluşturup iframe URL'ini HTML template'e gönderin.

// main.go
package main

import (
    "bytes"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "html/template"
    "io/ioutil"
    "net/http"
    "strconv"
    "time"
)

const (
    APIKey    = "vf_your_api_key"
    SecretKey = "your_secret_key"
    BaseURL   = "https://www.verifly.net/api"
)

type VerificationPayload struct {
    Phone      string   `json:"phone"`
    Methods    []string `json:"methods"`
    Lang       string   `json:"lang"`
    WebhookURL string   `json:"webhookUrl,omitempty"`
}

type VerificationResponse struct {
    Success bool                   `json:"success"`
    Data    map[string]interface{} `json:"data"`
}

func createSignature(payload interface{}) (string, string, error) {
    timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
    jsonData, _ := json.Marshal(payload)
    data := timestamp + string(jsonData)
    
    h := hmac.New(sha256.New, []byte(SecretKey))
    h.Write([]byte(data))
    signature := hex.EncodeToString(h.Sum(nil))
    
    return signature, timestamp, nil
}

func createVerification(phone string) (string, error) {
    payload := VerificationPayload{
        Phone:      phone,
        Methods:    []string{"sms", "whatsapp"},
        Lang:       "tr",
        WebhookURL: "https://yoursite.com/webhook",
    }
    
    signature, timestamp, _ := createSignature(payload)
    jsonData, _ := json.Marshal(payload)
    
    req, _ := http.NewRequest("POST", BaseURL+"/verify/create", bytes.NewBuffer(jsonData))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-API-Key", APIKey)
    req.Header.Set("X-Signature", signature)
    req.Header.Set("X-Timestamp", timestamp)
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    
    body, _ := ioutil.ReadAll(resp.Body)
    var result VerificationResponse
    json.Unmarshal(body, &result)
    
    // Return iframe URL
    return result.Data["iframeUrl"].(string), nil
}

// Verification page handler
func verifyHandler(w http.ResponseWriter, r *http.Request) {
    phone := r.URL.Query().Get("phone")
    if phone == "" {
        phone = "05551234567" // Default
    }
    
    iframeURL, err := createVerification(phone)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    tmpl := `
<!DOCTYPE html>
<html>
<head>
    <title>Telefon Doğrulama</title>
    <style>
        body { margin: 0; padding: 20px; font-family: Arial; }
        iframe { width: 100%; height: 600px; border: 1px solid #ddd; border-radius: 8px; }
    </style>
</head>
<body>
    <h1>Telefon Numaranızı Doğrulayın</h1>
    <iframe src="{{.IframeURL}}" title="Verifly Verification"></iframe>
</body>
</html> ` t, _ := template.New("verify").Parse(tmpl) t.Execute(w, map[string]string{"IframeURL": iframeURL}) } // Webhook handler func webhookHandler(w http.ResponseWriter, r *http.Request) { signature := r.Header.Get("X-Verifly-Signature") timestamp := r.Header.Get("X-Verifly-Timestamp") body, _ := ioutil.ReadAll(r.Body) // Signature doğrula data := timestamp + string(body) h := hmac.New(sha256.New, []byte(SecretKey)) h.Write([]byte(data)) expected := hex.EncodeToString(h.Sum(nil)) if signature != expected { http.Error(w, "Invalid signature", http.StatusUnauthorized) return } var payload map[string]interface{} json.Unmarshal(body, &payload) if payload["event"] == "verification.success" { // Kullanıcının veritabanında verified olarak işaretle println("✅ Verified:", payload["sessionId"]) } json.NewEncoder(w).Encode(map[string]bool{"received": true}) } func main() { http.HandleFunc("/verify", verifyHandler) http.HandleFunc("/webhook", webhookHandler) println("Server running on :8080") http.ListenAndServe(":8080", nil) }

🚀 Çalıştırma:

go run main.go
# Tarayıcıda: http://localhost:8080/verify?phone=05551234567

✅ Tüm örnekler production-ready! Kendi API key'lerinizi ekleyip hemen kullanmaya başlayabilirsiniz.