Sniip Identity API
Add enterprise-grade identity verification to your product in minutes. No SDK required.
https://id.sniip.com — All API endpoints require an X-API-Key header. Get your key from the Dashboard.Quick Start
Sniip Identity uses a secure three-step flow. Your server uploads a document, the user completes a liveness check on our hosted page, and you receive results via webhook. No biometric data or API keys ever appear in the browser.
verify_url for the liveness check
# The entire integration in one command
curl -X POST https://id.sniip.com/api/v1/verify/session \
-H 'X-API-Key: si_live_abc123...' \
-F 'reference_id=user_12345' \
-F 'redirect_url=https://yourapp.com/done' \
-F 'webhook_url=https://yourapp.com/webhook' \
-F 'document=@drivers_licence.jpg'
# Response:
{
"session_id": "9c77d4c1035102ce25556972d6d1392b",
"verify_url": "https://id.sniip.com/verify?token=a1b2c3..."
}
Authentication
All API requests require an X-API-Key header. Keys are generated from the Dashboard and scoped to your tenant account.
X-API-Key: si_live_a1b2c3d4e5f6g7h8i9j0...
API keys start with si_live_ followed by 32 random characters. Store keys securely and never expose them in client-side code or public repositories.
Verification Flow
The verify flow is the primary integration pattern. It handles the complete identity verification lifecycle: document upload, liveness detection, face comparison, and result delivery.
How It Works
- Your server creates a session by uploading the ID document with your API key. This returns a unique
session_idand a ready-to-useverify_url. - You redirect the user to the
verify_url. The URL contains only an opaque token — no API key, no document URL, no sensitive data. - The user completes a liveness check on our hosted page (camera-based, works on any modern browser).
- Sniip Identity compares the live selfie against the uploaded document and determines face match and liveness.
- Results are delivered two ways: the user is redirected back to your
redirect_url, and full results are POSTed to yourwebhook_url.
Create Verification Session
Creates a new verification session. Upload the reference document (government ID photo) and specify where to send results. The response contains a session_id (for tracking) and a verify_url (for redirecting the user).
Request Parameters
Send as multipart/form-data:
Response
{
"session_id": "9c77d4c1035102ce25556972d6d1392b",
"verify_url": "https://id.sniip.com/verify?token=a1b2c3d4e5f6..."
}
| Field | Type | Description |
|---|---|---|
session_id | string | Unique 32-character hex identifier for this verification session. Use this to query results and correlate webhooks. |
verify_url | string | Absolute URL to redirect the user to for the liveness check. Ready to use — no base URL prefix needed. |
Redirect the User
Redirect the user to the verify_url returned by the Create Session endpoint. The URL is absolute and ready to use — no assembly required:
https://id.sniip.com/verify?token=a1b2c3d4e5f6789012345678abcdef01
The user completes a liveness check on our hosted page. After completion:
- The user is redirected to your
redirect_urlwith thesession_idas the only query parameter - Full results are sent to your
webhook_urlvia HTTP POST - Your server should call the API with the
session_idto retrieve verification results
Redirect Query Parameters
https://yourapp.com/done?session_id=a1b2c3d4
Verification results (similarity scores, match status, liveness confidence) are never exposed in the redirect URL or to the end user’s browser. Use the webhook payload or call the API with the session_id to retrieve results.
Webhook Results
After verification completes, we POST the full result to your webhook_url. This is the authoritative source of truth — always use webhook data for business decisions.
{
"event": "verification.completed",
"session_id": "9c77d4c1035102ce25556972d6d1392b",
"reference_id": "user_12345",
"status": "completed",
"timestamp": "2026-02-14T12:00:00Z",
"result": {
"liveness": true,
"liveness_confidence": 98.7,
"liveness_status": "Succeeded",
"face_match": true,
"face_confidence": 96.4,
"face_message": "Face match found with 96.4% similarity"
}
}
Webhook Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Sniip-Signature | HMAC-SHA256 signature of the request body for verifying authenticity |
X-Sniip-Event | Event type — always verification.completed |
Result Fields
| Field | Type | Description |
|---|---|---|
event | string | Always verification.completed |
session_id | string | 32-character hex session identifier (matches the session_id from Create Session) |
reference_id | string | The reference ID you provided when creating the session |
result.liveness | boolean | true if the person is live (not a spoof, mask, or deepfake) |
result.liveness_confidence | number | Liveness confidence score (0–100) |
result.face_match | boolean | true if the live selfie matches the uploaded document |
result.face_confidence | number | Face similarity score (0–100). Above 80 indicates a match. |
result.face_message | string | Human-readable result description |
GET /liveness/results
Retrieve liveness detection results for a session. Returns the liveness status, confidence score, and optionally the reference image captured during the check.
{
"is_live": true,
"confidence": 98.7,
"status": "Succeeded",
"has_reference_image": true,
"reference_image_base64": "/9j/4AAQSkZJRg..."
}
Code Examples
Complete integration examples for popular languages. Each example shows the full flow: create session, redirect user, and handle webhook.
const fetch = require('node-fetch');
const FormData = require('form-data');
const fs = require('fs');
const crypto = require('crypto');
// Step 1: Create session and redirect user
app.get('/verify-user', async (req, res) => {
const form = new FormData();
form.append('reference_id', req.user.id);
form.append('redirect_url', 'https://yourapp.com/done');
form.append('webhook_url', 'https://yourapp.com/api/webhook');
form.append('document', fs.createReadStream(req.user.idPhotoPath));
const resp = await fetch('https://id.sniip.com/api/v1/verify/session', {
method: 'POST',
headers: { 'X-API-Key': process.env.SNIIP_API_KEY },
body: form,
});
const { session_id, verify_url } = await resp.json();
console.log('Session created:', session_id);
res.redirect(verify_url);
});
// Step 2: Handle webhook results
app.post('/api/webhook', (req, res) => {
// Verify signature
const sig = req.headers['x-sniip-signature'];
const expected = crypto
.createHmac('sha256', process.env.SNIIP_API_KEY)
.update(JSON.stringify(req.body))
.digest('hex');
if (sig !== expected) return res.status(401).send('Invalid signature');
const { reference_id, result } = req.body;
console.log('Verified:', reference_id,
'live:', result.liveness,
'match:', result.face_match,
'confidence:', result.face_confidence);
// Update your user record
res.sendStatus(200);
});
import os, hmac, hashlib, requests
from flask import Flask, redirect, request, jsonify
app = Flask(__name__)
API_KEY = os.environ['SNIIP_API_KEY']
# Step 1: Create session and redirect
@app.route('/verify-user')
def start_verify():
resp = requests.post(
'https://id.sniip.com/api/v1/verify/session',
headers={'X-API-Key': API_KEY},
data={
'reference_id': 'user_456',
'redirect_url': 'https://yourapp.com/done',
'webhook_url': 'https://yourapp.com/hook',
},
files={'document': open('id_photo.jpg', 'rb')},
)
data = resp.json()
print('Session:', data['session_id'])
return redirect(data['verify_url'])
# Step 2: Handle webhook
@app.route('/hook', methods=['POST'])
def webhook():
# Verify HMAC signature
sig = request.headers.get('X-Sniip-Signature', '')
expected = hmac.new(
API_KEY.encode(), request.data, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(sig, expected):
return 'Invalid signature', 401
data = request.json
print('Verified:', data['reference_id'],
'match:', data['result']['face_match'])
return '', 200
// create-session.php - Step 1
$apiKey = getenv('SNIIP_API_KEY');
$ch = curl_init('https://id.sniip.com/api/v1/verify/session');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . $apiKey],
CURLOPT_POSTFIELDS => [
'reference_id' => $userId,
'redirect_url' => 'https://yoursite.com/done',
'webhook_url' => 'https://yoursite.com/webhook.php',
'document' => new CURLFile('id_photo.jpg'),
],
]);
$result = json_decode(curl_exec($ch), true);
error_log('Session: ' . $result['session_id']);
header('Location: ' . $result['verify_url']);
exit;
// webhook.php - Step 2
$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_SNIIP_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $body, $apiKey);
if (!hash_equals($expected, $sig)) {
http_response_code(401);
exit('Invalid signature');
}
$data = json_decode($body, true);
$match = $data['result']['face_match'];
http_response_code(200);
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"mime/multipart"
"net/http"
"os"
)
// Step 1: Create session
func createSession(refID, docPath string) (string, error) {
body := &bytes.Buffer{}
w := multipart.NewWriter(body)
w.WriteField("reference_id", refID)
w.WriteField("redirect_url", "https://yourapp.com/done")
w.WriteField("webhook_url", "https://yourapp.com/hook")
fw, _ := w.CreateFormFile("document", "id.jpg")
f, _ := os.Open(docPath)
io.Copy(fw, f)
f.Close()
w.Close()
req, _ := http.NewRequest("POST",
"https://id.sniip.com/api/v1/verify/session", body)
req.Header.Set("X-API-Key", os.Getenv("SNIIP_API_KEY"))
req.Header.Set("Content-Type", w.FormDataContentType())
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result struct{
SessionID string `json:"session_id"`
VerifyURL string `json:"verify_url"`
}
json.NewDecoder(resp.Body).Decode(&result)
// result.SessionID = "9c77d4c1..." (use for tracking)
return result.VerifyURL, nil
}
// Step 2: Webhook handler
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
sig := r.Header.Get("X-Sniip-Signature")
mac := hmac.New(sha256.New, []byte(os.Getenv("SNIIP_API_KEY")))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(sig), []byte(expected)) {
http.Error(w, "Invalid signature", 401)
return
}
var payload map[string]interface{}
json.Unmarshal(body, &payload)
w.WriteHeader(200)
}
Webhook Security
Every webhook payload is signed with an HMAC-SHA256 hash using your API key as the secret. Always verify the X-Sniip-Signature header before processing webhook data.
Verification Steps
- Read the raw request body as bytes (before JSON parsing)
- Compute the HMAC-SHA256 of the body using your API key as the secret
- Compare the computed hex digest to the
X-Sniip-Signatureheader value - Reject the request if they do not match
hmac.compare_digest (Python), crypto.timingSafeEqual (Node), or hmac.Equal (Go) to prevent timing attacks.Error Handling
The API uses standard HTTP status codes. Error responses include a JSON body with an error field.
| Status | Meaning | Common Cause |
|---|---|---|
400 | Bad Request | Missing required fields, invalid file format |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | Tenant account deactivated or feature not available on plan |
404 | Not Found | Invalid session ID or verification not found |
409 | Conflict | Session already processed (duplicate callback) |
429 | Too Many Requests | Rate limit exceeded or Free plan verification quota reached |
500 | Server Error | Internal error — contact support if persistent |
{
"error": "reference_id is required"
}
Rate Limits
Rate limits are applied per API key and vary by plan:
| Plan | Requests / Second | Verifications / Month | Overage |
|---|---|---|---|
| Free | 5 | 100 (hard cap) | Blocked at limit |
| Starter | 20 | 1,000 | $0.15 / verification |
| Pro | 100 | 10,000 | $0.10 / verification |
| Enterprise | Custom | Custom | Custom |
When rate limited, the API returns 429 Too Many Requests with a Retry-After header indicating seconds to wait.