Complete reference for integrating WhatsApp OTP authentication, messaging, devices, and webhooks into your application.
https://api.loginwa.com/api/v1
Auth: Bearer <API_KEY>
/auth/start with the user's phone number./auth/verify with the session_id and OTP code.# Send OTP
curl -X POST https://api.loginwa.com/api/v1/auth/start \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"6281234567890"}'
# Verify OTP
curl -X POST https://api.loginwa.com/api/v1/auth/verify \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"session_id":"SESSION_ID","otp_code":"123456"}'
Authorization: Bearer <API_KEY> header.X-Api-Key: <API_KEY> header.application/json.For mobile/SPA apps, call LoginWA from your backend server to keep the API key secure.
// Example header
Authorization: Bearer sk_live_abc123xyz789
Send OTP to a phone number via WhatsApp.
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone | string | Yes | Phone number with country code (e.g., 6281234567890) |
| country_code | string | No | Default country code if not in phone |
| otp_length | int | No | OTP length (default: 6) |
| message_template | string | No | Custom message with {code} placeholder |
| meta | object | No | Custom metadata to track |
{
"session_id": "3bbaaf0b-3c11-44a2-8a7e-4edc426c5fcd",
"expires_in": 300,
"sent_via_engine": true
}
Verify OTP code entered by user.
| Parameter | Type | Required | Description |
|---|---|---|---|
| session_id | string | Yes | Session ID from /auth/start |
| otp_code | string | Yes | OTP code entered by user |
{
"status": "verified",
"phone": "6281234567890",
"verified_at": "2026-02-05T12:00:00Z",
"meta": {"user_id": "123"}
}
Send custom WhatsApp messages beyond OTP.
Send a WhatsApp message to a phone number.
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone | string | Yes | Recipient phone number with country code |
| message | string | Yes | Message content to send |
| device_id | string | No | Specific device ID to use (optional) |
curl -X POST https://api.loginwa.com/api/v1/messages/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"6281234567890","message":"Hello from LoginWA!"}'
Manage WhatsApp devices connected to your app.
List all devices for your app.
// Response
{
"devices": [
{
"id": "dev_abc123",
"name": "My WhatsApp",
"phone": "6281234567890",
"status": "connected",
"created_at": "2026-01-15T10:00:00Z"
}
]
}
Create a new device and get QR code.
| Parameter | Type | Required |
|---|---|---|
| name | string | Yes |
Get device details including QR code if pending.
Refresh QR code for device pairing.
Delete a device and disconnect WhatsApp.
Send bulk WhatsApp messages to multiple recipients with smart delay, scheduling, and progress tracking.
Hello {name}!| Status | Description |
|---|---|
draft | Campaign created, not started |
queued | Waiting to be processed |
sending | Currently sending messages |
paused | Temporarily stopped |
completed | All messages sent |
failed | Campaign failed |
List all your broadcast campaigns.
Create a new broadcast campaign.
| Parameter | Type | Required |
|---|---|---|
| name | string | Yes |
| message | string | Yes |
| contacts | array | Yes |
| contacts.*.phone | string | Yes |
| contacts.*.name | string | No |
| contacts.*.variables | object | No |
| media_url | string | No |
| media_type | string | No (image/video/document/audio) |
| delay_seconds | integer | No (default: 5) |
| schedule_at | datetime | No |
Get campaign details and progress.
Start sending the campaign.
Pause an active campaign.
Resume a paused campaign.
List contacts with delivery status. Filter with ?status=sent|failed|pending
Delete a campaign (must be paused first).
// 1. Create campaign
POST /api/v1/broadcast/campaigns
{
"name": "Summer Sale 2026",
"message": "Hello {name}!\n\nSpecial offer for you:\n- 50% discount\n- Free shipping\n\nClick: https://shop.com/promo",
"contacts": [
{"phone": "081234567890", "name": "Budi"},
{"phone": "081234567891", "name": "Ani", "variables": {"city": "Jakarta"}},
{"phone": "081234567892", "name": "Citra"}
],
"delay_seconds": 5,
"media_url": "https://example.com/promo.jpg",
"media_type": "image"
}
// Response
{
"success": true,
"data": {
"id": "camp_abc123xyz",
"name": "Summer Sale 2026",
"status": "draft",
"total_contacts": 3,
"estimated_duration": "15 seconds"
}
}
// 2. Start sending
POST /api/v1/broadcast/campaigns/camp_abc123xyz/send
// 3. Check progress
GET /api/v1/broadcast/campaigns/camp_abc123xyz
{
"success": true,
"data": {
"id": "camp_abc123xyz",
"status": "sending",
"progress": {
"total": 3,
"sent": 2,
"delivered": 1,
"failed": 0,
"pending": 1,
"percentage": 66.7
}
}
}
Configure webhooks to receive real-time events (incoming messages, status updates, etc.).
| Event | Description |
|---|---|
message.received | New incoming WhatsApp message |
message.sent | Message successfully sent |
message.failed | Message delivery failed |
device.connected | Device paired successfully |
device.disconnected | Device disconnected |
otp.verified | OTP verification successful |
{
"event": "message.received",
"timestamp": "2026-02-05T12:00:00Z",
"data": {
"message_id": "msg_abc123",
"from": "6281234567890",
"body": "Hello!",
"device_id": "dev_xyz"
}
}
List all webhooks.
Create a new webhook.
| Parameter | Type | Required |
|---|---|---|
| url | string | Yes |
| events | array | Yes |
Update webhook URL or events.
Delete a webhook.
Send a test event to the webhook.
Generate new signing secret.
Each webhook includes a signature in the X-Webhook-Signature header. Verify it to ensure authenticity:
// PHP/Laravel example
$signature = $request->header('X-Webhook-Signature');
$payload = $request->getContent();
$secret = 'your_webhook_secret';
$expected = hash_hmac('sha256', $payload, $secret);
if (! hash_equals($expected, $signature)) {
abort(403, 'Invalid signature');
}
$client = new \GuzzleHttp\Client();
// Send OTP
$res = $client->post('https://api.loginwa.com/api/v1/auth/start', [
'headers' => [
'Authorization' => 'Bearer ' . env('LOGINWA_API_KEY'),
'Content-Type' => 'application/json',
],
'json' => [
'phone' => '6281234567890',
'otp_length' => 6,
],
]);
$data = json_decode($res->getBody(), true);
$sessionId = $data['session_id'];
const BASE_URL = 'https://api.loginwa.com/api/v1';
const API_KEY = process.env.LOGINWA_API_KEY;
async function sendOtp(phone) {
const res = await fetch(`${BASE_URL}/auth/start`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone }),
});
if (!res.ok) throw new Error('Failed to send OTP');
return res.json();
}
import requests
import os
BASE_URL = 'https://api.loginwa.com/api/v1'
API_KEY = os.environ.get('LOGINWA_API_KEY')
def send_otp(phone):
res = requests.post(
f'{BASE_URL}/auth/start',
headers={
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
},
json={'phone': phone}
)
res.raise_for_status()
return res.json()
# Send OTP
curl -X POST https://api.loginwa.com/api/v1/auth/start \
-H "Authorization: Bearer $LOGINWA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"6281234567890"}'
# Verify OTP
curl -X POST https://api.loginwa.com/api/v1/auth/verify \
-H "Authorization: Bearer $LOGINWA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"session_id":"xxx","otp_code":"123456"}'
Start free, then scale as you grow. All plans include full API access, Widget, SDK, Dashboard, Webhooks, and unlimited projects.
| Limit Type | Value |
|---|---|
| API Rate Limit | 120 requests/minute per API key |
| OTP Length | 6 digits |
| OTP TTL | 5 minutes (300 seconds) |
| Max Verify Attempts | 5 attempts |
| Monthly Quota | Based on your plan (see pricing) |
Exceeding rate limits returns 429 Too Many Requests. Exceeding quota returns quota_exceeded error.
| Code | HTTP | Description |
|---|---|---|
unauthorized | 401 | Invalid or missing API key |
invalid_phone | 422 | Phone number format invalid |
invalid_code | 422 | OTP code is incorrect |
expired | 422 | OTP session has expired |
max_attempts | 422 | Too many verification attempts |
quota_exceeded | 429 | Monthly message quota exceeded |
rate_limited | 429 | Too many requests per minute |
no_device | 503 | No connected WhatsApp device |
Having trouble with integration? We're here to help.