API v1 · REST · OTP over WhatsApp

API Reference

Send and verify OTP codes over WhatsApp. Use this page for quick integration and error handling.

Base URL: https://api.loginwa.com/api/v1 Auth: Authorization: Bearer <SECRET_API_KEY> Dashboard

Quickstart

  1. Log in → create an app → copy the Secret API key.
  2. Call /auth/start with the user phone number.
  3. Call /auth/verify with the returned session_id and the OTP code entered by the user.
curl -X POST https://api.loginwa.com/api/v1/auth/start \
  -H "Authorization: Bearer <SECRET_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"phone":"6281234567890","meta":{"user_id":"123"}}'

curl -X POST https://api.loginwa.com/api/v1/auth/verify \
  -H "Authorization: Bearer <SECRET_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"session_id":"<SESSION_ID>","otp_code":"123456"}'

Authentication

  • Use Authorization: Bearer <SECRET_API_KEY> (or X-Api-Key).
  • Content type: application/json.
  • Each key has its own quota and rate limit (default 120 req/min).
curl -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     https://api.loginwa.com/api/v1/auth/start

Use different keys per project to isolate traffic, usage, and logs.

SDKs & Downloads

Use the SDKs or plugin for the fastest integration path. For other stacks, call the REST endpoints below.

Endpoints

/auth/start

FieldTypeRequiredExample
phonestringrequired6281234567890
country_codestringoptional62
otp_lengthintoptional6
message_templatestringoptionalOTP code {code}
metaobjectoptional{"user_id":"123"}
{
  "session_id": "3bbaaf0b-3c11-44a2-8a7e-4edc426c5fcd",
  "expires_in": 300,
  "sent_via_engine": true
}

Errors: 401 unauthorized, 422 invalid_phone, 429 quota_exceeded.

/auth/verify

FieldTypeRequiredExample
session_idstringrequiredsess_xxx
otp_codestringrequired123456
{
  "status": "verified",
  "phone": "6281234567890",
  "verified_at": "2025-11-28T12:00:00Z"
}

Errors: 401 unauthorized, 422 invalid_code | expired | blocked.

Rules & limits

  • Rate limit: per API key, default 120 requests / minute.
  • Quota: tied to your plan; exceeding quota returns quota_exceeded.
  • OTP length: 6 digits. Max attempts: 5.
  • TTL: OTP valid for 5 minutes (300 seconds).

Use dashboard logs to monitor success rate, failures, and per-app usage.

Errors & handling

  • 401 unauthorized — missing/invalid API key.
  • 422 invalid_phone — phone format cannot be parsed.
  • 422 invalid_code / expired / max_attempts — verification failed.
  • 429 quota_exceeded — monthly quota finished for this key.

Handle 4xx gracefully and let users retry or request a new OTP after cooldown.

Logging & errors

OTP error codes

CodeHTTPMeaning
invalid_phone422Phone format invalid
invalid_code422OTP incorrect
expired422OTP expired
max_attempts422Verification attempts exceeded
quota_exceeded429Plan quota exceeded
unauthorized401Invalid API key

Examples

PHP · Node · cURL

PHP (Guzzle)

$client = new \GuzzleHttp\Client();
$res = $client->post('https://api.loginwa.com/api/v1/auth/start', [
  'headers' => [ 'Authorization' => 'Bearer '.getenv('API_KEY') ],
  'json' => [ 'phone' => '6281234567890' ]
]);
$body = json_decode((string) $res->getBody(), true);

Node (fetch)

const res = await fetch('https://api.loginwa.com/api/v1/auth/start', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + process.env.API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ phone: '6281234567890' })
});
const data = await res.json();

cURL

curl -X POST https://api.loginwa.com/api/v1/auth/start \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone":"6281234567890"}'

Webhook Signature

Set header x-engine-hook-secret on the sender (engine). Verify it server-side:

// Laravel example
if (! hash_equals(config('wa_engine.secret'), $request->header('x-engine-hook-secret'))) {
    abort(403, 'invalid signature');
}

Use different secrets per environment (staging/production) to avoid webhook mix-ups.

Support

Need help with integration, higher limits, or production cutover?

Email: [email protected] Dashboard: logs & usage per app View pricing