# Verification Hub Integration Guide

**Version:** 1.0.0
**Last Updated:** 2026-06-23

The Verification Hub provides a highly secure, flexible, and comprehensive multi-channel identity verification system (WhatsApp & Email). This document outlines the best practices for integrating the Verification Hub into your own frontend applications, flow builders, and backend services.

---

## Overview

Integrating verification involves a standard 3-step process:

1. **Request Verification**: Your app requests the API to start a verification flow for an `identifier` (e.g. phone number or email).
2. **Handle User Input (OTP / Magic Link)**: The user receives a message and either enters an OTP code into your frontend or clicks a Magic Link.
3. **Verify and Proceed**: Your app sends the OTP code to the Verification API, or the Magic Link validates automatically. If successful, you proceed with the user's intended action (e.g., creating an account, allowing a login).

---

## Authentication

All API requests require authentication using API keys. You can obtain API keys from the Verification Hub Dashboard.

### Required Headers

| Header | Description |
|--------|-------------|
| `X-Public-Key` | Your public API key (required) |
| `X-Timestamp` | Current Unix timestamp (required for signed requests) |
| `X-Signature` | HMAC-SHA256 signature (optional but recommended) |

### Example Authentication

```php
// PHP Example
$publicKey = 'vpub_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$secretKey = 'vsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

// Generate timestamp and signature
$timestamp = time();
$body = json_encode(['profile_id' => 'xxx', 'identifier' => '+1234567890', ...]);
$payload = $timestamp . '.' . 'POST' . '.' . '/api/v1/verification/request' . '.' . $body;
$signature = base64_encode(hash_hmac('sha256', $payload, $secretKey, true));

$response = Http::withHeaders([
    'X-Public-Key' => $publicKey,
    'X-Timestamp' => (string) $timestamp,
    'X-Signature' => $signature,
    'Content-Type' => 'application/json',
])->post('https://your-domain.com/api/v1/verification/request', $data);
```

### Signature Generation

The signature is computed as:

```
base64_encode(HMAC-SHA256(timestamp + '.' + method + '.' + path + '.' + body, secret_key))
```

**Security Notes:**
- Signatures older than 5 minutes are rejected (replay attack protection)
- Use HTTPS for all API requests
- Never expose your secret key in client-side code

---

## Creating a Verification Profile

Before any verification can happen, you need a **Verification Profile**.
Profiles define the rules for a specific verification flow, such as:
- Which channel to use (`whatsapp`, `email`)
- OTP length and expiry time
- Allowed IP whitelists and Country Codes
- Captcha enforcement rules (`disabled`, `always`, `risk_based`, `after_x_attempts`)

**Best Practice:** Create separate profiles for different use cases. For example, have a `Login Profile` and a `Checkout Profile` with different security strictness.

---

## API Endpoints

### Base URL

```
https://your-domain.com/api/v1/verification
```
*(Note: `/v1/verification` is also supported as an alias)*

---

### Mobile Configuration Discovery

Allows mobile applications (iOS, Android, Flutter, React Native) to dynamically discover profile rules, active WhatsApp bot phone numbers, OTP lengths, and polling intervals at runtime without hardcoding or requiring app updates.

**Endpoint:** `GET /api/v1/verification/config`

**Headers:**
```
X-Public-Key: vpub_your_public_key
```

#### Mandatory Headers:

| Header | Required | Description |
|--------|----------|-------------|
| `X-Public-Key` | Yes (for mobile/client apps) | Your API Key public token (`vpub_...`). Alternatively `X-Secret-Key` or `Authorization: Bearer <token>` for backend servers |

*(No query or request body parameters)*

**Response (Success):**

```json
{
    "status": "active",
    "profile_id": "c1f7b8e2-...",
    "profile_name": "Default Verification Profile",
    "channels": ["whatsapp", "email"],
    "default_channel": "whatsapp",
    "otp": {
        "length": 6,
        "type": "numeric",
        "expiry_minutes": 5,
        "expiry_seconds": 300,
        "resend_cooldown_seconds": 60,
        "max_attempts": 3
    },
    "whatsapp": {
        "enabled": true,
        "one_click_enabled": true,
        "bot_phone": "15551234567"
    },
    "polling": {
        "status_url_template": "/api/v1/verification/status/{request_id}",
        "recommended_interval_ms": 2000,
        "timeout_seconds": 120
    }
}
```

---

### Request Verification

Initiates a verification request and dispatches an OTP or generates 1-Click WhatsApp intent URLs.

**Endpoint:** `POST /api/v1/verification/request`

**Headers:**
```
X-Public-Key: vpub_your_public_key
X-Timestamp: 1719123456
X-Signature: base64_signature (optional)
```

**Request Body:**

```json
{
    "identifier": "+1234567890",
    "profile_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "channel": "whatsapp",
    "purpose": "login",
    "ip_address": "203.0.113.42",
    "user_agent": "Mozilla/5.0...",
    "captcha_token": "03AFcWeA4...",
    "silent": false
}
```

#### Mandatory Parameters:

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `identifier` | string | Yes (Mandatory) | Destination phone number (E.164 format, e.g. `+1234567890`) or email address |

#### Optional Parameters:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `profile_id` | string (UUID) | Auto-resolved | UUID of Verification Profile. Auto-resolved from API key or company default if omitted |
| `channel` | string | `whatsapp` | Delivery channel: `whatsapp` or `email` |
| `purpose` | string | `default` | Context/reason for verification (e.g. `login`, `register`, `checkout`, 1-50 chars) |
| `trust_key` | string | None | Previous client/device trust token to bind or check for cached verification |
| `ip_address` | string | Auto-captured | Client IP address for fraud engine & rate limits |
| `user_agent` | string | Auto-captured | Client user agent string for auditing |
| `captcha_token` | string | None | Google reCAPTCHA token (required only if profile mandates captcha enforcement) |
| `silent` | boolean | `false` | If `true`, outbound OTP message is NOT sent (ideal for WhatsApp 1-Click auto-login) |

**Response (Success):**

```json
{
    "success": true,
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "cached": false,
    "reused": false,
    "otp_rules": {
        "length": 6,
        "type": "numeric",
        "expiry_seconds": 300,
        "resend_in_seconds": 60
    },
    "mobile_actions": {
        "one_click_whatsapp": {
            "enabled": true,
            "intent_url": "whatsapp://send?phone=15551234567&text=Verify%20Request%3A+550e8400...",
            "universal_url": "https://wa.me/15551234567?text=Verify%20Request%3A+550e8400..."
        },
        "polling_url": "/api/v1/verification/status/550e8400-..."
    }
}
```

**Response (Cached - Skip Verification):**

```json
{
    "success": true,
    "cached": true,
    "verified": true,
    "trust_key": "trust_abc123..."
}
```

---

### Validate OTP Code

Validates an OTP submitted by the user.

**Endpoint:** `POST /api/v1/verification/verify`

**Request Body:**

```json
{
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "code": "123456"
}
```

#### Mandatory Parameters:

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `request_id` | string (UUID) | Yes (Mandatory) | The request ID returned from the `/request` endpoint |
| `code` | string | Yes (Mandatory) | The OTP code (1-20 characters, typically 4-8 digits) entered by the user |

*(No optional parameters)*

**Response (Success):**

```json
{
    "verified": true,
    "trust_key": "trust_abc123..."
}
```

**Response (Failed - Invalid Code):**

```json
{
    "verified": false
}
```

**Response (Error - e.g., expired, blocked):**

```json
{
    "error": "Maximum verification attempts reached. Try again in 30 minute(s)."
}
```

---

### Check Verification Status

Retrieves the current status of a verification request. Used by frontends polling during 1-Click WhatsApp flows or backend services consuming the trust key.

**Endpoint:** `GET /api/v1/verification/status/{request_id}`

#### Mandatory Path Parameters:

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `request_id` | string (UUID) | Yes (Mandatory) | The UUID of the verification request session to inspect |

*(No optional parameters)*

**Response (Verified):**

```json
{
    "status": "verified",
    "is_verified": true,
    "trust_key": "trust_abc123...",
    "identifier": "+1234567890",
    "channel": "whatsapp",
    "verified_at": "2026-06-23T10:30:00+00:00"
}
```

**Response (Pending):**

```json
{
    "status": "pending",
    "is_verified": false
}
```

Possible statuses: `pending`, `verified`, `expired`, `failed`, `blocked`, `rate_limited`

---

### Resend OTP

Initiates a resend of the OTP (if the original hasn't expired).

> **Tip (Recommended for Mobile Apps):** Instead of storing `request_id` and handling expired states manually, mobile apps can simply call `POST /api/v1/verification/request` again with the user's `identifier`. If an unexpired request exists, the server automatically reuses the same OTP code, extends the expiration window, re-sends the message, and returns `"reused": true` with the same `request_id`. If expired, it seamlessly generates a new request.

**Endpoint:** `POST /api/v1/verification/resend`

**Request Body:**

```json
{
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```

#### Mandatory Parameters:

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `request_id` | string (UUID) | Yes (Mandatory) | The pending request ID to resend the code for |

*(No optional parameters)*

**Response:**

```json
{
    "success": true,
    "message": "Resend initiated"
}
```

---

### Magic Link Verification (Public)

Opens the user's browser when they click a magic link from the verification message.

**Endpoint:** `GET /api/v1/verification/magic/{request_id}/{token}`

**Rate Limit:** 10 requests per minute per IP

#### Mandatory Path Parameters:

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `request_id` | string (UUID) | Yes (Mandatory) | The UUID of the verification request session |
| `token` | string | Yes (Mandatory) | Cryptographic verification token sent in the magic link email |

*(No optional parameters. Public browser endpoint, no API key required)*

**Response (Success):**
HTML page indicating successful verification.

**Response (Failed):**
HTML page indicating the link is invalid or expired.

---

## Webhook Integration

Instead of polling the API, you can configure webhooks to receive real-time notifications.

### Configuring Webhooks

1. Go to your Verification Profile settings
2. Add a webhook URL
3. Select which events to receive:
   - `verification.requested`
   - `verification.successful`
   - `verification.failed`

### Webhook Payload

```json
{
    "event": "verification.successful",
    "data": {
        "request_id": "550e8400-e29b-41d4-a716-446655440000",
        "identifier": "+1234567890"
    },
    "timestamp": "2026-06-23T10:30:00Z"
}
```

### Webhook Headers

| Header | Description |
|--------|-------------|
| `X-Webhook-Event` | The event type (e.g., `verification.successful`) |
| `X-Webhook-Timestamp` | Unix timestamp when the webhook was sent |

### Best Practices

- Always return HTTP 200 within 10 seconds to acknowledge receipt
- Process webhooks asynchronously (queue them for processing)
- Verify webhook authenticity using the event timestamp (reject if too old)
- Implement idempotency using the `request_id`

---

## Fraud Prevention Features

The Verification Hub includes built-in fraud prevention:

### OTP Bombing Protection
Blocks an identifier after too many requests within a time window.

### IP Velocity Limiting
Limits the number of unique identifiers that can be verified from a single IP.

### Disposable Email Blocking
Prevents common disposable email domains (mailinator.com, 10minutemail.com, etc.)

### reCAPTCHA Integration
Optionally require Google reCAPTCHA verification:
- `disabled` - No captcha required
- `always` - Always required
- `risk_based` - Required based on risk score
- `after_x_attempts` - Required after failed verification attempts

### IP/Country Whitelisting
Restrict verification to specific IPs, domains, or countries.

---

## Rate Limiting

| Endpoint | Limit |
|----------|-------|
| All authenticated endpoints | 30 requests/minute |
| Magic link verification | 10 requests/minute |

Rate limit headers are included in responses:
- `X-RateLimit-Limit`: Maximum requests allowed
- `X-RateLimit-Remaining`: Remaining requests in current window
- `X-RateLimit-Reset`: Unix timestamp when the limit resets

---

## Error Codes

| HTTP Status | Error | Description |
|-------------|-------|-------------|
| 400 | Bad Request | Invalid request parameters |
| 401 | Unauthorized | Missing or invalid API key |
| 401 | Invalid signature | HMAC signature validation failed |
| 404 | Not found | Verification request not found |
| 422 | Validation failed | Request validation errors |
| 429 | Too Many Requests | Rate limit exceeded |
| 429 | OTP Bombing protection triggered | Too many requests to this identifier |
| 429 | Velocity protection triggered | Too many unique identifiers from this IP |

---

## SDKs and Libraries

Official SDKs are available for:
- PHP (Laravel)
- JavaScript/Node.js
- Python
- Ruby

Coming soon.

---

## Support

For technical support, contact:
- Email: support@your-domain.com
- Documentation: https://docs.your-domain.com/verification
