Verifly API Documentation

Modern, secure, and easy 2FA verification system

Introduction

Verifly is a modern 2FA platform that allows you to verify your users' phone numbers or emails. Unlike traditional OTP systems, it uses an inbound verification approach - meaning users send codes to you, not the other way around.

Why Verifly?

💰

Cost Effective

Instead of sending outbound SMS, the user sends you a message

📱

Multi-Channel

SMS, WhatsApp, Voice Call, Email support

🚫

No Spam

SMS spam folder problem is eliminated

Real-time

Socket.io and Webhook support

How Does It Work?

  1. 1. Create a verification session in your backend
  2. 2. A QR code is displayed to the user
  3. 3. User scans the QR code and sends the verification code via their chosen method
  4. 4. Verifly checks the verification code and notifies you of the result via webhook

Quick Start

ℹ️ Prerequisites: Node.js 14+ or PHP 7.4+ must be installed

Step 1: Account Creation

First, create an account from the registration page. After email verification, you can access the dashboard.

Step 2: Application Creation

Click the "New Application" button from the Dashboard and enter the following information:

Application Name: E.g.: "My Website"
Application URL: E.g.: https://example.com
Notification API: Verification results will be sent to this API.
Services: SMS, WhatsApp, Call, Email (select what you want)

Step 3: Getting API Keys

After creating the application, you will be given two keys:

API Key (Public)

vf_abc123def456ghi789...

Can be used in frontend, it's okay if it's visible

Secret Key (Private)

96fd73b0c28fff2d9cfd9dc35...

⚠️ Use ONLY in backend! Never add to frontend.

Step 4: Creating Your First Verification Session

Create a verification session in your backend. Here's a Node.js example:

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

const API_KEY = 'vf_abc123...';  // from dashboard
const SECRET_KEY = '96fd73b0c28fff2d9cfd9dc35...'; // from dashboard

// 1. Create signature (for security)
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. Create session
async function createVerificationSession(phone) {
  const payload = {
    phone: phone,           // e.g.: '05461234567'
    methods: ['sms', 'whatsapp'], // Allowed methods
    lang: 'tr',            // Language
    timeout: 5,            // Timeout in minutes (1-15)
    data: 'user_123_or_jwt_token' // Custom data to return in webhook
  };
  
  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;
}

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

Step 5: Add Iframe to Frontend

Display the verification interface using the iframeUrl you received from the backend:

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

<script>
// Listen for verification result
window.addEventListener('message', function(event) {
  // Security: Accept only messages from Verifly
  if (event.origin !== 'https://www.verifly.net') return;
  
  const { type, sessionId, reason } = event.data;
  
  switch(type) {
    case 'VERIFICATION_SUCCESS':
      console.log('✅ Verification successful!', sessionId);
      // Redirect user
      window.location.href = '/dashboard';
      break;
      
    case 'VERIFICATION_FAILED':
      console.log('❌ Verification failed!', reason);
      alert('Verification failed: ' + reason);
      break;
      
    case 'VERIFICATION_EXPIRED':
      console.log('⏰ Verification expired', sessionId);
      alert('Verification expired. Please try again.');
      break;

    case 'VERIFICATION_ABORTED':
      console.log('❌ Verification aborted by user', sessionId);
      alert('Verification aborted by user.');
      break;
  }
});
</script>

Iframe Event Types

postMessage events sent from iframe to parent window:

VERIFICATION_SUCCESS

Verification completed successfully.

Payload:

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

Use Cases:

  • Redirect user to dashboard
  • Show verified user badge
  • Send notification to backend via ajax
  • Update user profile
VERIFICATION_FAILED

Verification failed (maximum attempts exceeded).

Payload:

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

Use Cases:

  • Show error message
  • Show "Try Again" button
  • Create new session
  • Suggest alternative verification method
  • Offer support team contact option
VERIFICATION_EXPIRED

Session expired (default: 2 minutes).

Payload:

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

Use Cases:

  • Show "Session expired" message
  • Show "Start New Verification" button
  • Create new session
  • Redirect user to home page
VERIFICATION_ABORTED

Verification cancelled by user.

Payload:

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

Use Cases:

  • Show "Verification cancelled" message
  • Show "Start New Verification" button
  • Create new session
  • Redirect user to home page
💡 Advanced Event Management

1. Event Logger: Send all events to analytics

2. State Management: Update session state in Redux/Vuex store

3. UI Feedback: Show toast/snackbar notifications

4. Retry Logic: Automatically create new session on Failed/Expired states

5. A/B Testing: Test different event scenarios

Advanced Example: React Integration

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 control
      
      switch(type) {
        case 'VERIFICATION_SUCCESS':
          setStatus('success');
          // Analytics event
          gtag('event', 'verification_success', { session_id: sid });
          // Notify backend
          fetch('/api/user/verify-complete', { 
            method: 'POST',
            body: JSON.stringify({ sessionId: sid })
          });
          // Close after 2 seconds
          setTimeout(() => window.location.href = '/dashboard', 2000);
          break;
          
        case 'VERIFICATION_FAILED':
          setStatus('failed');
          // Analytics event
          gtag('event', 'verification_failed', { session_id: sid });
          // Show error message
          toast.error('Verification failed. Please try again.');
          break;
          
        case 'VERIFICATION_EXPIRED':
          setStatus('expired');
          // Create new session
          createNewSession();
          break;
      }
    };
    
    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, [sessionId]);
  
  return (
    <div className="modal">
      {status === 'success' && (
        <div className="success-message">✅ Verification Successful!</div>
      )}
      <iframe src={iframeUrl} width="100%" height="600" />
    </div>
  );
}

✅ Congratulations! You've set up your first verification system. Now you can proceed to API details.

Custom UI / Headless API Usage

If you don't want to use an iframe or are developing a mobile application, you can create your own verification UI by using Verifly APIs directly.

📱 Use Cases
  • iOS/Android Mobile Apps - React Native, Flutter, Swift, Kotlin
  • Custom Web UI - Verification page that fits your design
  • Desktop Applications - Electron, Tauri, .NET
  • API-First Systems - Headless commerce, microservices

Overview

Verifly API is designed to work completely headlessly. You can manage all verification processes through API endpoints.

🔌 API Endpoints to Use

POST
/api/verify/create

Create session

GET
/api/verify/:sessionId

Check session status

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

Select and start verification method

POST
/api/verify/:sessionId/cancel

Cancel current method

POST
/api/verify/:sessionId/abort

Completely abort session

Flow Diagram

1️⃣ Create Session
POST /api/verify/create
2️⃣ Select Method (SMS/WhatsApp/Call/Email)
POST /api/verify/:sessionId/select-method
3️⃣ Show Information in UI
QR code, verification code, service contact
4️⃣ Listen to Status
Socket.IO or Polling (GET /api/verify/:sessionId)
✅ Verification Successful!
status: 'verified'

Mobile App Example (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. Create session
  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. Select method
  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');
    
    // Start polling
    startPolling();
  };

  // 3. Status polling (instead of Socket.IO)
  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); // Check every 2 seconds
  };

  // 4. Open SMS app
  const openSMSApp = () => {
    const url = `sms:${verificationData.selectedServiceContact}?body=${verificationData.verificationCode}`;
    Linking.openURL(url);
  };

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

  return (
    <View style={styles.container}>
      {status === 'idle' && (
        <Button title="Start Verification" onPress={createSession} />
      )}

      {sessionId && status === 'loading' && (
        <View>
          <Text>Select Method:</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}>Send Verification Code</Text>
          
          <Image
            source={{ uri: verificationData.qrCodeData }}
            style={styles.qrCode}
          />
          
          <Text style={styles.label}>Send message to this number:</Text>
          <Text style={styles.contact}>{verificationData.selectedServiceContact}</Text>
          
          <Text style={styles.label}>Message content:</Text>
          <Text style={styles.code}>{verificationData.verificationCode}</Text>
          
          {verificationData.method === 'sms' && (
            <Button title="Open SMS App" onPress={openSMSApp} />
          )}
          {verificationData.method === 'whatsapp' && (
            <Button title="Open WhatsApp" onPress={openWhatsApp} />
          )}
          
          <Text style={styles.waiting}>Waiting for verification...</Text>
        </View>
      )}

      {status === 'success' && (
        <View style={styles.successContainer}>
          <Text style={styles.successText}>✅ Verification Successful!</Text>
        </View>
      )}

      {status === 'failed' && (
        <View style={styles.failedContainer}>
          <Text style={styles.failedText}>❌ Verification Failed</Text>
          <Button title="Try Again" 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 Example (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 connection
  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');
        // Success! Redirect user
        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}>Start Verification</button>
      ) : !verificationData ? (
        <div className="method-selection">
          <h3>Select Verification Method</h3>
          <button onClick={() => selectMethod('sms')}>📱 SMS</button>
          <button onClick={() => selectMethod('whatsapp')}>💬 WhatsApp</button>
          <button onClick={() => selectMethod('call')}>📞 Voice Call</button>
        </div>
      ) : status === 'waiting' ? (
        <div className="verification-waiting">
          <h3>Send Verification Code</h3>
          
          <img src={verificationData.qrCodeData} alt="QR Code" />
          
          <p><strong>{verificationData.selectedServiceContact}</strong> to</p>
          <p className="code">{verificationData.verificationCode}</p>
          <p>send the code</p>
          
          {verificationData.method === 'sms' && (
            <a href={`sms:${verificationData.selectedServiceContact}?body=${verificationData.verificationCode}`}>
              Send SMS
            </a>
          )}
          
          <div className="spinner">Waiting for verification...</div>
        </div>
      ) : status === 'success' ? (
        <div className="success">
          <h2>✅ Verification Successful!</h2>
          <p>Redirecting...</p>
        </div>
      ) : (
        <div className="failed">
          <h2>❌ Verification Failed</h2>
          <button onClick={createSession}>Try Again</button>
        </div>
      )}
    </div>
  );
}

Polling Method (Without Socket.IO)

If you can't use Socket.IO, you can check the status by calling the GET /api/verify/:sessionId endpoint at regular intervals.

// Polling function
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;
        
        // Call callback
        callback(status, data.data);
        
        // Terminal states - stop polling
        if (['verified', 'failed', 'expired'].includes(status)) {
          clearInterval(pollInterval);
        }
      }
    } catch (error) {
      console.error('Polling error:', error);
    }
  }, 2000); // Check every 2 seconds
  
  // Return cleanup function
  return () => clearInterval(pollInterval);
};

// Usage
const stopPolling = startStatusPolling(sessionId, (status, data) => {
  console.log('Status:', status);
  
  if (status === 'verified') {
    alert('✅ Verification successful!');
    window.location.href = '/dashboard';
  } else if (status === 'failed') {
    alert('❌ Verification failed!');
  } else if (status === 'expired') {
    alert('⏰ Session expired!');
  }
});

// Stop when component unmounts
// stopPolling();
⚠️ Important Notes
  • Interval: 2-3 seconds is ideal (don't make requests too frequently)
  • Timeout: Poll for the duration of the session (max 15 minutes)
  • Cleanup: Clear the interval when component unmounts
  • Error Handling: Apply exponential backoff on network errors
  • Battery: Consider battery consumption on mobile devices

✅ Custom UI Ready! Now you can create your own verification UI without needing an iframe. You can complete backend integration with webhooks.

API Authentication

Verifly API secures every request with HMAC-SHA256 signing. This method prevents request tampering and validates signatures that only you can create.

🔐 Security Layers

  • ✓ Prevents man-in-the-middle attacks
  • ✓ Detects request tampering
  • ✓ Prevents replay attacks (timestamp validation)
  • ✓ Requires unique signature for each request

Required Headers

The following headers must be sent with each API request:

Header Description Required
X-API-Key Your application's public API key ✓ Yes
X-Signature HMAC-SHA256 signature (in hex format) ✓ Yes
X-Timestamp Unix timestamp (in milliseconds) ✓ Yes
Content-Type application/json ✓ Yes

How to Create a Signature?

📋 Signature Creation Steps

  1. 1. Get current time in milliseconds: Date.now()
  2. 2. Convert request body to JSON string: JSON.stringify(payload)
  3. 3. Combine timestamp and JSON: timestamp + jsonString
  4. 4. Create HMAC-SHA256 hash with Secret Key
  5. 5. Convert hash to hexadecimal format

Node.js Example Code

const crypto = require('crypto');

function createSignature(payload, secretKey) {
  // 1. Get timestamp
  const timestamp = Date.now().toString();
  
  // 2. Convert payload to JSON string
  const jsonPayload = JSON.stringify(payload);
  
  // 3. Create data string
  const data = timestamp + jsonPayload;
  
  // 4. Create HMAC-SHA256 hash
  const signature = crypto
    .createHmac('sha256', secretKey)
    .update(data)
    .digest('hex');
  
  return { signature, timestamp };
}

// Usage example
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 Example Code

<?php

function createSignature($payload, $secretKey) {
    // 1. Get timestamp (milliseconds)
    $timestamp = (string)(time() * 1000);
    
    // 2. Convert payload to JSON
    $jsonPayload = json_encode($payload);
    
    // 3. Create data string
    $data = $timestamp . $jsonPayload;
    
    // 4. Create HMAC-SHA256 hash
    $signature = hash_hmac('sha256', $data, $secretKey);
    
    return [
        'signature' => $signature,
        'timestamp' => $timestamp
    ];
}

// Usage example
$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 Example Code

import hmac
import hashlib
import json
import time

def create_signature(payload, secret_key):
    # 1. Get timestamp (milliseconds)
    timestamp = str(int(time.time() * 1000))
    
    # 2. Convert payload to JSON
    json_payload = json.dumps(payload)
    
    # 3. Create data string
    data = timestamp + json_payload
    
    # 4. Create HMAC-SHA256 hash
    signature = hmac.new(
        secret_key.encode('utf-8'),
        data.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return {
        'signature': signature,
        'timestamp': timestamp
    }

# Usage example
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"]}')

⚠️ Important Security Notes

  • NEVER use Secret Key in frontend code
  • Store Secret Key as an environment variable
  • Don't commit to Git repository (add .env file to .gitignore)
  • Requests with timestamps older than 5 minutes are rejected (replay attack prevention)
  • A new signature must be created for each request

Testing

A simple example to test your signature creation function:

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 follows RESTful design principles. All requests start with the base URL https://www.verifly.net/api.

📌 General Information

  • Base URL: https://www.verifly.net/api
  • Format: JSON (application/json)
  • Encoding: UTF-8
  • Rate Limit: 100 req/min (session creation), 1000 req/min (others)
POST /verify/create

Creates a new verification session

Request Body

Parametre Tip Açıklama Zorunlu
phone string Phone number (e.g., "05461234567"). If not provided, user enters it in iframe. No
email string Email address. If not provided, user enters it in iframe. No
methods array Allowed methods: ["sms", "whatsapp", "call", "email"] No
lang string Language code: "tr" or "en" (default: "tr") No
timeout number Session duration (minutes): 1-15 range (default: 2) No
webhookUrl string Webhook URL to receive verification result No
redirectUrl string URL to redirect after successful verification No
data string Custom data (JSON, JWT token, or any string) - will be returned in webhook on success No

💡 Note: phone or email parameters are optional. If not provided, user enters this information in iframe based on selected method.

💡 Note: webhookUrl parameter is optional. If not provided, result is posted to webhookURL in application settings.

Example 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 Descriptions

sessionId

Unique session ID. Used in all operations.

iframeUrl

Verification page URL to embed in iframe.

allowedMethods

Available verification methods.

userInputRequired

Whether user needs to enter phone/email.

GET /verify/:sessionId

Queries session status and details

URL Parameters

sessionId - Unique ID received when creating session

Example 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 Values

Status Description
pending User hasn't selected method yet
waiting Method selected, waiting for verification code
success Verification completed successfully
expired Session expired
cancelled User cancelled
POST /verify/:sessionId/select-method

Selects verification method and starts verification process (for Custom UI)

URL Parameters

sessionId - Session ID

Request Body

Parameter Type Description Required
method string Selected method: "sms", "whatsapp", "call", "email" ✓ Yes
recipientContact string Phone/email (required if not provided in create) No*

Example 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"
  }
}

💡 Important: This endpoint is critical when using Custom UI. Response includes verificationCode and selectedServiceContact, you show these to user to send verification code.

Response Field Descriptions

verificationCode

6-digit code user needs to send

selectedServiceContact

Phone/email address to send code to

qrCodeData

QR code PNG data (base64), for quick sending by scanning

status

"waiting" - Waiting for verification code

POST /verify/:sessionId/cancel

Cancels current verification method, user can select another method

URL Parameters

sessionId - Session ID

💡 Usage: Session stays open, only selected method is reset. User can try another method. Use /abort for complete cancellation.

Example 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

Completely aborts and closes session (no return)

URL Parameters

sessionId - Session ID

⚠️ Warning: This operation is irreversible! Session status is marked as "failed" and webhook is sent. You must create new session for new verification.

Example 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

Queries your account balance

Example 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"
  }
}

⚠️ Error Codes

400 Bad Request

Invalid parameter or missing field

401 Unauthorized

Invalid API Key or Signature

404 Not Found

Session not found

429 Too Many Requests

Rate limit exceeded

402 Payment Required

Insufficient balance

Webhooks

Webhooks automatically send a POST request to your server when a verification process is completed. This allows you to receive real-time notifications and automate user operations.

✨ Webhook Advantages

Real-time notifications
No need for polling
Secure HMAC validation
Automatic retry mechanism

Setting Up Webhook URL

You can set up the webhook URL in two ways:

1️⃣ Application Level

Set a default webhook URL for each application from the Dashboard.

Settings → Webhook URL

2️⃣ Session Based

Specify a custom URL with the webhookUrl parameter when creating a session.

POST /verify/create

Webhook Payload

When verification is completed, a POST request is sent to your webhook URL in the following format:

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 Types

Event Description
verification.success Verification completed successfully.
verification.failed Verification failed.
verification.expired Allowed time for verification has expired.
verification.cancelled New verification started and current verification was cancelled.
verification.aborted User aborted the verification.

Webhook Security (HMAC Validation)

Each webhook request is signed with the X-Verifly-Signature header. You can verify this signature to ensure the request truly comes from Verifly.

🔒 Signature Verification Steps

  1. 1. Get X-Verifly-Signature and X-Verifly-Timestamp from headers
  2. 2. Check that timestamp is not older than 5 minutes (replay attack prevention)
  3. 3. Convert webhook payload to JSON string
  4. 4. Create data string: timestamp + jsonPayload
  5. 5. Create HMAC-SHA256 hash with Secret Key
  6. 6. Compare your generated hash with the received X-Verifly-Signature using timingSafeEqual
  7. 7. Reject the request if they don't match (return 401)

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. Get info from headers
  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 check (5 minutes)
  const now = Date.now();
  if (Math.abs(now - parseInt(timestamp)) > 5 * 60 * 1000) {
    return res.status(401).json({ error: 'Timestamp too old' });
  }
  
  // 3. Convert payload to JSON string
  const payload = JSON.stringify(req.body);
  const data = timestamp + payload;
  
  // 4. Create our own signature
  const expectedSignature = crypto
    .createHmac('sha256', SECRET_KEY)
    .update(data)
    .digest('hex');
  
  // 5. Securely compare signatures
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
    console.log('⚠️ Invalid webhook signature!');
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // 6. Process based on event type
  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);
    
    // Update database, activate user, etc.
    // 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. Return 200 OK
  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. Get info from headers
$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 check (5 minutes)
$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. Create signature data (timestamp + payload)
$signatureData = $timestamp . $payload;

// 5. Create our own signature
$expectedSignature = hash_hmac('sha256', $signatureData, $secretKey);

// 6. Securely compare signatures
if (!hash_equals($expectedSignature, $signature)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// 7. Process based on event type
$event = $data['event'];
$sessionId = $data['sessionId'];
$status = $data['status'];

switch ($event) {
    case 'verification.success':
        error_log("✅ Verification successful: $sessionId");
        // Update database
        // 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. Return 200 OK
http_response_code(200);
echo json_encode(['received' => true]);
?>

Retry Mechanism

If a webhook request fails (timeout, 5xx error, etc.), Verifly automatically retries:

1st Attempt

Immediately

2nd Attempt

After 30 seconds

3rd Attempt

After 5 minutes

💡 Best Practices

  • Your webhook endpoint should return 200 OK (within 5 seconds)
  • Queue heavy operations, respond to webhook quickly
  • Make idempotent operations for each webhook (no issues if same request comes twice)
  • Use HTTPS (HTTP webhook URLs are rejected)
  • Don't skip signature verification (creates security vulnerability)

Testing

You can use these tools in development environment to test your webhooks:

🌐 ngrok

Converts your local server to a public URL

ngrok http 3000

🪝 webhook.site

Visualizes incoming webhooks

webhook.site

Passwordless Login

With Verifly OAuth, you can enable your users to log in to your applications without entering a password.

✨ Advantages

No password required, more secure
One-click login, easy integration
Supports Email, SMS, WhatsApp, Call
Ready-to-use JavaScript SDK

Overview

Verifly OAuth enables your users to log in with their email or phone number without using a password. Integrate with a single JavaScript SDK.

Login Flow

OAuth flow consists of 4 steps:

1
User Clicks Button

Clicks the 'Sign in with Verifly' button created by the SDK

2
Redirected to Initiate Endpoint

Redirected to /oauth/verifly/initiate endpoint on your backend, signature is generated

3
Redirect to Verifly OAuth Page

Redirected to www.verifly.net/oauth page, user enters email/phone and verifies

4
Callback and Session Creation

If verification is successful, redirected to callback URL, backend verifies session and user logs in

1. SDK Installation

Start by adding the Verifly OAuth SDK to your page.

Add SDK

<!-- Add to HEAD section -->
<script src="https://www.verifly.net/js/verifly-oauth.js"></script>

Use SDK

<!-- Create container for button -->
<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 Options

Parameter Type Description
initiateUrl required Initiate endpoint URL on your backend
siteName required Site name to display on OAuth page
buttonId required ID of the div where button will be created
buttonText optional Text on the button. Default: 'Sign in with Verifly'
buttonStyle optional Button style: 'default', 'light', 'dark'. Default: 'default'

2. Initiate Endpoint

When user clicks the button, SDK calls this endpoint. Here you should generate signature and redirect to Verifly OAuth page.

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

    // Get credentials from environment
    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');
    }

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

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

    // Get query parameters
    const theme = req.query.theme || 'default';
    const site = req.query.site || 'Verifly';
    const methods = req.query.methods || '';

    // Build OAuth URL
    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)}`;
    }

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

OAuth URL Parameters

Parameter Description
client_key API Key
timestamp Timestamp (milliseconds)
signature HMAC-SHA256 signature (calculated with timestamp)
callback_url URL to redirect when verification is successful
site Site name to display on OAuth page
theme Theme: 'default', 'light', 'dark'
methods Allowed verification methods (comma-separated): 'email,sms,whatsapp,call'

3. Callback Endpoint

After user completes verification, Verifly redirects to this endpoint. Here you should verify session and complete user login.

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

    // Check session_id parameter
    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;

    // Generate signature for verify
    const timestamp = Date.now();
    const signatureData = `${timestamp}${session_id}`;
    const signature = crypto
      .createHmac('sha256', SECRET_KEY)
      .update(signatureData)
      .digest('hex');

    // Send verify request to Verifly API
    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;

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

    // Find user in database
    let user = await User.findOne({
      $or: [
        { email: sessionData.email },
        { phone: sessionData.phone }
      ]
    });

    if (!user) {
      // Create new user if not exists
      user = await User.create({
        email: sessionData.email,
        phone: sessionData.phone,
        verified: true,
        verifiedAt: sessionData.verifiedAt
      });
    }

    // Save to session
    req.session.userId = user._id;
    req.session.email = sessionData.email;

    // Redirect to dashboard
    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. Full Integration Example

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>
  
  <!-- Container where SDK button will be created -->
  <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;

  // Log user in
  req.session.userEmail = email;
  req.session.userPhone = phone;

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

module.exports = router;

💡 Best Practices

  • ✓ Store API Key and Secret Key in environment variables
  • ✓ Never send secret key to frontend
  • ✓ Always verify signature in callback endpoint
  • ✓ Store session data securely
  • ✓ Use HTTPS and configure callback URLs correctly

Code Examples

Complete working integration examples for different programming languages and frameworks.

Node.js / Express - Full Integration

// 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';

// Create signature
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 };
}

// Create session
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'];
  
  // Verify signature
  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' });
  }
  
  // Process event
  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') {
            // Mark user as verified
        }
        
        return response()->json(['received' => true]);
    }
}
?>

Python / Flask - Simple Integration

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 with WebView Integration

💡 Recommended Approach: Display iframe URL with WebView in React Native. Check API Endpoints section for Custom UI details.

// 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(() => {
    // Create session and get iframe URL
    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);
      // Notify backend or navigate
    }
  };

  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',
  },
});

📦 Required Packages:

npm install react-native-webview crypto-js

Swift (iOS) - Iframe Integration with WKWebView

💡 Recommended Approach: Display iframe URL with WKWebView. Check API Endpoints section for Custom UI details.

// 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)
        
        // Create session and load iframe
        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ı!")
                // Notify backend or navigate
            }
        }
    }
    
    deinit {
        webView?.configuration.userContentController.removeScriptMessageHandler(forName: "verifly")
    }
}

Kotlin (Android) - Iframe Integration with WebView

💡 Recommended Approach: Display iframe URL with WebView. Check API Endpoints section for Custom UI details.

// 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)
        
        // Create session and load iframe
        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")
                // Notify backend or navigate
            }
        }
    }
}

📦 Gradle Dependencies:

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

image/svg+xml Go - Iframe Integration with Web Server

💡 Recommended Approach: Create session on backend and send iframe URL to HTML template.

// 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>Phone Verification</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>Verify Your Phone Number</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) // Verify signature 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" { // Mark user as verified in database 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) }

🚀 Run:

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

✅ All examples are production-ready! You can start using them immediately by adding your own API keys.