WapFlow OTP API reference

Send and verify verification codes from your own WhatsApp number. All responses are JSON.

Base URL: https://wapflow.net/api/otp/v1

Authentication

Send the header Authorization: Bearer <key> with every request. You create keys on the WapFlow OTP page of your WapFlow panel; live keys start with wfo_live_ and test keys with wfo_test_. A key is shown only once, when it is created — store it safely and use it on your server only.

A successful response looks like { "success": true, ... }; an error looks like { "success": false, "error": { "code": "...", "message": "..." } }.

Send a code

POST/send

phone (required) is the recipient's number. Optional: code_length (4–8), ttl (60–900 s), lang (e.g. tr, en) and app_name. Anything you leave out comes from your panel settings.

Request
{
  "phone": "905321234567",
  "code_length": 6,
  "ttl": 300,
  "lang": "tr",
  "app_name": "Acme"
}
Response (200)
{
  "success": true,
  "request_id": "req_...",
  "phone": "905321234567",
  "expires_at": "2026-11-12T10:05:00.000Z",
  "status": "sent"            // "sent" | "queued"
}

Verify a code

POST/verify

phone and code are required. With request_id that exact request is checked; without it, the latest unexpired code sent to that number with this key. After 5 wrong attempts the request is locked, and a verified code can't be reused.

Request
{ "request_id": "req_...", "phone": "905321234567", "code": "123456" }
Response (200)
{ "success": true, "valid": true }

{ "success": true, "valid": false,
  "reason": "invalid_code",     // invalid_code | expired | too_many_attempts | already_verified | not_found
  "attempts_left": 3 }

Check status

GET/status/:request_id

Returns the request status: queued, sent, delivered, read, failed, verified or expired.

{ "success": true, "request_id": "req_...", "status": "delivered",
  "sent_at": "...", "delivered_at": "...", "verified_at": null }

Test the connection

GET/me

Shows that the key is valid, your plan, the number of connected accounts and this month's quota usage (the WordPress plugin's "Test connection" button uses it).

{ "success": true, "plan": "otp-2",
  "accounts": { "limit": 2, "connected": 1 },
  "quota": { "monthly": 15000, "used": 42, "period": "2026-11" },
  "ends_at": "2026-12-12T10:00:00.000Z", "mode": "live" }

Examples

These examples send a code and then verify what the user enters. Replace wfo_live_XXXX with your own key.

# 1) send
curl -X POST https://wapflow.net/api/otp/v1/send \
  -H "Authorization: Bearer wfo_live_XXXX" \
  -H "Content-Type: application/json" \
  -d '{"phone":"905321234567","app_name":"Acme"}'

# 2) verify
curl -X POST https://wapflow.net/api/otp/v1/verify \
  -H "Authorization: Bearer wfo_live_XXXX" \
  -H "Content-Type: application/json" \
  -d '{"request_id":"req_...","phone":"905321234567","code":"123456"}'
<?php
function wapflow_otp(string $path, array $body): array {
    $ch = curl_init('https://wapflow.net/api/otp/v1' . $path);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 15,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . getenv('WAPFLOW_OTP_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode($body),
    ]);
    $res = json_decode(curl_exec($ch), true) ?: [];
    curl_close($ch);
    return $res;
}

$sent = wapflow_otp('/send', ['phone' => '905321234567', 'app_name' => 'Acme']);
// ... kullanıcı kodu girince:
$check = wapflow_otp('/verify', ['request_id' => $sent['request_id'], 'phone' => '905321234567', 'code' => $_POST['code']]);
if (!empty($check['valid'])) { /* giriş başarılı */ }
const BASE = 'https://wapflow.net/api/otp/v1';

async function otp(path, body) {
  const res = await fetch(BASE + path, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.WAPFLOW_OTP_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });
  return res.json();
}

const sent = await otp('/send', { phone: '905321234567', app_name: 'Acme' });
// ... when the user submits the code:
const check = await otp('/verify', { request_id: sent.request_id, phone: '905321234567', code: '123456' });
if (check.valid) { /* signed in */ }
import os
import requests

BASE = "https://wapflow.net/api/otp/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["WAPFLOW_OTP_KEY"]}

def otp(path, body):
    return requests.post(BASE + path, json=body, headers=HEADERS, timeout=15).json()

sent = otp("/send", {"phone": "905321234567", "app_name": "Acme"})
# ... when the user submits the code:
check = otp("/verify", {"request_id": sent["request_id"], "phone": "905321234567", "code": "123456"})
if check.get("valid"):
    pass  # signed in

Error codes

HTTPCodeMeaning
400invalid_requestThe request body or parameters are invalid.
401invalid_keyThe API key is invalid or has been revoked.
402no_subscriptionThis account has no WapFlow OTP subscription.
402subscription_expiredThe subscription has expired; please renew it.
403sales_disabledThe service is temporarily unavailable.
422invalid_phoneThe phone number is invalid.
429rate_limitedRate limit exceeded; retry after the retry_after seconds in the response.
429quota_exceededThis month's code quota is used up.
429daily_limit_reachedThe daily sending limit of the connected WhatsApp accounts has been reached; retry after the retry_after seconds in the response (next day 00:00, Istanbul time). No quota is used.
503no_account_connectedThere is no connected, enabled WhatsApp account.
503send_failedThe message could not be sent; try again shortly.

Rate limits

Account warm-up and daily limit

Test keys

Keys starting with wfo_test_ send no WhatsApp message and use no quota; the code is returned in the test_code field. Sending and rate-limit rules are the same as for live keys, and an active subscription is still required. To go live, just swap the key.

{ "success": true, "request_id": "req_...", "status": "sent", "test_code": "482913" }

Phone number format

Security notes

WordPress plugin

  1. Download wapflow-otp.zip.
  2. In WordPress admin go to Plugins → Add New → Upload Plugin, upload the zip and activate it.
  3. In the plugin settings, paste the API key you created in the WapFlow panel.
  4. Use the "Test connection" button to check the key and your plan.
  5. Try it with a test key first, then switch to the live key.