Verification API
Create a verification session from your backend with your secret key (sk_live_…). Users place a missed call to the gateway number returned in the response.
cURL
curl -X POST "https://api.shernova.com/v1/verifications" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"phone_number":"+201012345678","client_request_id":"signup-001"}'Node.js
const res = await fetch('https://api.shernova.com/v1/verifications', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SHERNOVA_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone_number: '+201012345678',
client_request_id: 'signup-001',
}),
});
const session = await res.json();
console.log('Call:', session.gateway_phone_number);Python
import os, requests
resp = requests.post(
"https://api.shernova.com/v1/verifications",
headers={"Authorization": f"Bearer {os.environ['SHERNOVA_SECRET_KEY']}"},
json={"phone_number": "+201012345678", "client_request_id": "signup-001"},
timeout=30,
)
session = resp.json()
print("Call:", session["gateway_phone_number"])PHP
$ch = curl_init('https://api.shernova.com/v1/verifications');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SHERNOVA_SECRET_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'phone_number' => '+201012345678',
'client_request_id' => 'signup-001',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$session = json_decode(curl_exec($ch), true);Test keys (pk_test_ / sk_test_)
Use sk_test_ for backend test flows. Sessions use real gateways — call gateway_phone_number from the user's phone to complete:
Poll session status
curl "https://api.shernova.com/v1/verifications/SESSION_UUID" \
-H "Authorization: Bearer sk_test_YOUR_KEY"Receipt verification (backend)
Verify receipt once
const res = await fetch('https://api.shernova.com/v1/receipts/verify', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SHERNOVA_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ receipt: receiptJwtFromMobile }),
});
if (res.status === 409) {
// receipt_already_used — replay attack
}Flutter SDK
Complete sign-up flow
import 'package:flutter/material.dart';
import 'package:shernova_core/shernova_core.dart';
import 'package:shernova_ui/shernova_ui.dart';
import 'shernova_config.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Shernova.init(shernovaConfig);
runApp(const PhoneVerifyPage());
}
class PhoneVerifyPage extends StatefulWidget {
const PhoneVerifyPage({super.key});
@override
State<PhoneVerifyPage> createState() => _PhoneVerifyPageState();
}
class _PhoneVerifyPageState extends State<PhoneVerifyPage> {
final _phone = TextEditingController();
Future<void> _verify() async {
try {
final session = await verifyPhone(
context: context,
phoneNumber: _phone.text.trim(),
clientRequestId: 'signup-${DateTime.now().millisecondsSinceEpoch}',
);
if (session.status == 'verified' && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Verified: ${session.sessionId}')),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Verify phone')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(children: [
TextField(controller: _phone, decoration: const InputDecoration(labelText: 'Phone (+20…)')),
const SizedBox(height: 16),
FilledButton(onPressed: _verify, child: const Text('Verify')),
]),
),
);
}
}Backend Example (Node.js)
Create verification + poll
const API = 'https://api.shernova.com';
const API_KEY = process.env.SHERNOVA_SECRET_KEY;
async function createVerification(phoneNumber) {
const res = await fetch(`${API}/v1/verifications`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone_number: phoneNumber }),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
async function getSession(sessionId) {
const res = await fetch(`${API}/v1/verifications/${sessionId}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
return res.json();
}
// Usage:
const session = await createVerification('+201012345678');
console.log('Call:', session.gateway_phone_number);
// Poll until verified (prefer webhooks in production)
let status = session.status;
while (status === 'waiting') {
await new Promise(r => setTimeout(r, 3000));
({ status } = await getSession(session.session_id));
}
console.log('Final status:', status);Webhook Handler Example
Verify signatures against the raw request body. With Express, mount express.raw({ type: 'application/json' }) on the webhook route before any JSON parser.
Express.js
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/shernova', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-shernova-signature'] ?? '';
const secret = process.env.SHERNOVA_WEBHOOK_SECRET;
const expected = crypto.createHmac('sha256', secret).update(req.body).digest('hex');
const received = String(signature).replace(/^sha256=/, '');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString());
if (event.status === 'verified') {
// Mark user as verified in your database
console.log('Verified:', event.session_id, event.phone_number);
}
res.sendStatus(200);
});Do not use the legacy shernova package (1.x). Required: shernova_core. Optional: shernova_ui, shernova_cli (dev).
Webhooks
Verify HMAC signature (Node.js)
const crypto = require('crypto');
function verifySignature(rawBody, signatureHeader, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const received = String(signatureHeader).replace(/^sha256=/, '');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}Verify HMAC signature (Python)
import hmac, hashlib
def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
received = signature.removeprefix("sha256=")
return hmac.compare_digest(expected, received)Billing and credits
App billing summary
curl "https://api.shernova.com/v1/apps/YOUR_APP_ID/billing" \
-H "Authorization: Bearer YOUR_DASHBOARD_JWT" \
-H "X-Shernova-Organization-Id: YOUR_ORG_ID"Ledger entries
const res = await fetch(
`${process.env.SHERNOVA_API}/v1/apps/${appId}/billing/ledger?limit=20`,
{
headers: {
Authorization: `Bearer ${jwt}`,
'X-Shernova-Organization-Id': orgId,
},
},
);
console.log(await res.json());