WhatsApp & Email OTP verification
Verify phone-number or email ownership with secure six-digit one-time passwords. Create an OTP-only key or add these channels to a KYC key.
During API creation choose KYC, WhatsApp OTP only, Email OTP only, or WhatsApp + Email OTP. OTP-only keys cannot call KYC verification endpoints.
WhatsApp: send and verify
/v1/phone-verifications/v1/phone-verifications/:id/verifyUse an international E.164 number and a fresh Idempotency-Key for every new OTP request. A same-key retry returns the original request instead of sending another message or charging twice.
curl -X POST https://api.okias.io/v1/phone-verifications \
-H "Authorization: Bearer $OKIAS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "phone_number": "+923001234567" }'A successful live response has status: SENT, an OTP id, an expiry time and credits_charged: 10. SENT means Meta accepted the request—not that the handset delivered or read it.
curl -X POST https://api.okias.io/v1/phone-verifications/$OTP_ID/verify \
-H "Authorization: Bearer $OKIAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "code": "123456" }'Email: send and verify
/v1/email-verifications/v1/email-verifications/:id/verifyEmail OTP costs 1 credit when the transactional email provider accepts the live message. Verification is free and codes expire after ten minutes.
curl -X POST https://api.okias.io/v1/email-verifications \
-H "Authorization: Bearer $OKIAS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "email": "user@example.com" }'
# Verify with the returned id
curl -X POST https://api.okias.io/v1/email-verifications/$OTP_ID/verify \
-H "Authorization: Bearer $OKIAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "code": "123456" }'PHP example
Keep the OKIAS key in your server environment—never in browser or mobile-app code.
<?php
$ch = curl_init('https://api.okias.io/v1/phone-verifications');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('OKIAS_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['phone_number' => '+923001234567']),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($result['error']['message'] ?? 'OTP request failed');
}
$otpId = $result['id'];Billing and retries
| Request | Credits |
|---|---|
| Live WhatsApp OTP accepted by Meta | 10 |
| Live Email OTP accepted by the email provider | 1 |
| Provider rejection or transport failure at send time | 0 |
| OTP verification or status lookup | 0 |
| Sandbox OTP | 0 |
Live requests reserve the channel price before contacting the provider. Acceptance captures it; initial send failure releases it automatically. A later WhatsApp delivery failure does not reverse an already accepted send.
ok_test_ key never calls Meta or SMTP and never charges credits. Responses include sandbox_otp_code: 123456.Meta delivery-status webhook
Configure this callback URL in the Meta WhatsApp app to receive sent, delivered, read and delivery-failed updates:
https://api.okias.io/v1/webhooks/meta/whatsapp- Set a long random
WHATSAPP_WEBHOOK_VERIFY_TOKENin OKIAS and the same value in Meta's Verify Token field. - Configure
WHATSAPP_APP_SECRETfrom the Meta app. OKIAS verifies every POST withX-Hub-Signature-256. - Subscribe to WhatsApp
messageswebhook field. Invalid signatures are rejected and raw OTP/message content is not logged.
OTP status responses can progress from SENT to DELIVERED and READ. Duplicate or out-of-order callbacks cannot move a verified OTP backward.
Security controls
- One request per destination and API key every 60 seconds.
- Six-digit cryptographically random live codes and a five-attempt limit.
- WhatsApp codes expire after five minutes; email codes after ten minutes.
- Plaintext codes and full destinations are not stored in OTP records.
- OTP records are isolated to the account and API key that created them.
- Disabled channels return
403 PHONE_VERIFICATION_NOT_ENABLEDor403 EMAIL_VERIFICATION_NOT_ENABLED.