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. Create a verification session in your backend
- 2. A QR code is displayed to the user
- 3. User scans the QR code and sends the verification code via their chosen method
- 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
/api/verify/create
Create session
/api/verify/:sessionId
Check session status
/api/verify/:sessionId/select-method
Select and start verification method
/api/verify/:sessionId/cancel
Cancel current method
/api/verify/:sessionId/abort
Completely abort session
Flow Diagram
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. Get current time in milliseconds:
Date.now() - 2. Convert request body to JSON string:
JSON.stringify(payload) - 3. Combine timestamp and JSON:
timestamp + jsonString - 4. Create HMAC-SHA256 hash with Secret Key
- 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)
/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.
/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 |
/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
/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"
}
}
/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"
}
}
/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
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/jsonX-Verifly-Signature: hmac_sha256_hashX-Verifly-Timestamp: unix_timestamp_msX-Verifly-Attempt: attempt_numberUser-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. Get
X-Verifly-SignatureandX-Verifly-Timestampfrom headers - 2. Check that timestamp is not older than 5 minutes (replay attack prevention)
- 3. Convert webhook payload to JSON string
- 4. Create data string:
timestamp + jsonPayload - 5. Create HMAC-SHA256 hash with Secret Key
- 6. Compare your generated hash with the received
X-Verifly-SignatureusingtimingSafeEqual - 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
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:
User Clicks Button
Clicks the 'Sign in with Verifly' button created by the SDK
Redirected to Initiate Endpoint
Redirected to /oauth/verifly/initiate endpoint on your backend, signature is generated
Redirect to Verifly OAuth Page
Redirected to www.verifly.net/oauth page, user enters email/phone and verifies
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'
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.