NepalOTP Documentation
OTP-first verification infrastructure for Nepal.
What is NepalOTP?
NepalOTP is a purpose-built OTP verification platform designed specifically for the Nepali market. It handles the complete lifecycle of one-time passwords: generation, delivery via SMS, and verification. The platform treats OTP as critical infrastructure rather than a messaging feature.
Why OTP is treated as infrastructure
OTP verification is a security primitive. It requires predictable delivery, strict expiration enforcement, and abuse protection. NepalOTP separates OTP from general-purpose SMS gateways because verification demands different reliability guarantees than transactional messaging.
What NepalOTP provides
- Server-side OTP generation with cryptographic randomness
- OTP delivery via SMS to Nepali phone numbers
- Server-side OTP verification with attempt limits
- Sandbox mode for development and testing
- Rate limiting and abuse protection
- Approved transactional SMS templates for non-OTP use cases
What NepalOTP does not support
- Bulk SMS campaigns or mass messaging
- Marketing or promotional SMS
- Free-text SMS composition
- Email delivery
- Unapproved message content
Getting Started
1. Create an account
Register at nepalotp.com/login,
verify your email, and complete your profile. Enable two-factor authentication from
Settings → Two-Factor for additional security.
2. Create your first app
Go to Dashboard → Apps & API Keys and create an
application. Choose the environment (sandbox or production), configure rate limits, and
optionally add a webhook URL. Each app has a public app_id used in logs and
webhook payloads.
3. Generate an API key
Open your app and create an API key. Keys are environment-specific and must match the app environment.
API Key Format
npot_test_1a2b3c4d5e6f7g8h9i0j
4. Send your first sandbox OTP
Use the API key to send an OTP. Sandbox mode always returns OTP 123456 and does not deduct
credits.
5. Go live
Switch your app environment to production, top up credits, and generate a live API key
(prefixed with npot_live_). Use the live
key in production traffic.
Authentication
All API requests require authentication via an API key passed in the Authorization header.
Authorization: Bearer notp_live_sk_1a2b3c4d5e6f7g8h9i0j
Key rotation
You can have up to 3 active API keys at any time. When rotating keys:
- Generate a new key in the dashboard
- Deploy your application with the new key
- Revoke the old key once migration is complete
Security best practices
- Never expose API keys in client-side code
- Store keys in environment variables or secure vaults
- Use different keys for staging and production
- Rotate keys periodically and after team member departures
Core Concepts
OTP Lifecycle
Generate
Server creates OTP
Send
SMS delivered
Verify
User submits code
Expire
OTP invalidated
Server-side generation and verification
OTPs are generated and verified exclusively on NepalOTP servers. Your application never stores OTPs. When a user submits a code, your backend sends it to NepalOTP for verification.
Expiry and attempt limits
| Parameter | Default | Description |
|---|---|---|
expiry |
5 minutes | Time until OTP becomes invalid |
max_attempts |
3 | Failed attempts before OTP is invalidated |
length |
6 digits | OTP length (fixed) |
Why clients should never store OTPs
Storing OTPs client-side creates security vulnerabilities. Client storage can be inspected, logged, or intercepted. Server-side verification ensures the OTP is validated against a secure source of truth.
Sending an OTP
/v1/otp/send
Generates a new OTP and sends it via SMS to the specified phone number. The OTP message uses a fixed template format.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
phone |
string | Yes | Nepali phone number (10 digits, starting with 97 or 98) |
channel |
string | No | Delivery channel. Default: sms |
reference |
string | No | Your internal reference ID for tracking |
Fixed OTP template
Your verification code is: {OTP}. Valid for 5 minutes. Do not share this code.
Example request
curl -X POST https://api.nepalotp.com/v1/otp/send \
-H "Authorization: Bearer notp_live_sk_1a2b3c4d5e6f7g8h9i0j" \
-H "Content-Type: application/json" \
-d '{
"phone": "9841234567",
"reference": "user_signup_abc123"
}'
Success response
{
"success": true,
"data": {
"otp_id": "otp_8f9a7b6c5d4e3f2a",
"phone": "9841234567",
"expires_at": "2024-01-15T10:35:00Z",
"attempts_remaining": 3
}
}
Common errors
INVALID_PHONE_FORMAT
Phone number must be a 10-digit Nepali number starting with 97 or 98.
RATE_LIMIT_EXCEEDED
Too many OTP requests for this phone number. Wait before retrying.
Verifying an OTP
/v1/otp/verify
Verifies an OTP submitted by the user. The OTP is invalidated after successful verification or when the maximum attempt limit is reached.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
otp_id |
string | Yes | The OTP ID returned from the send endpoint |
code |
string | Yes | The 6-digit code entered by the user |
Example request
curl -X POST https://api.nepalotp.com/v1/otp/verify \
-H "Authorization: Bearer notp_live_sk_1a2b3c4d5e6f7g8h9i0j" \
-H "Content-Type: application/json" \
-d '{
"otp_id": "otp_8f9a7b6c5d4e3f2a",
"code": "847293"
}'
Success response
{
"success": true,
"data": {
"verified": true,
"phone": "9841234567"
}
}
Failure response
{
"success": false,
"error": {
"code": "INVALID_OTP",
"message": "The code is incorrect",
"attempts_remaining": 2
}
}
Sandbox Mode
Sandbox mode allows you to integrate and test the NepalOTP API without sending real SMS messages or incurring charges.
Sandbox behavior
No SMS delivery
Messages are logged but not sent to real phones.
Fixed OTP: 123456
All sandbox OTPs are 123456 for
predictable testing.
No balance deduction
Your wallet balance is not affected by sandbox requests.
Identical API responses
Response structure matches production exactly.
Identifying sandbox mode
Sandbox API keys are prefixed with notp_sandbox_ while
live keys use notp_live_.
Moving to live mode
- Complete your integration testing in sandbox mode
- Navigate to Dashboard → Settings → Live Mode
- Submit a request describing your use case and expected volume
- Add funds to your wallet once approved
- Generate a live API key and deploy
Transactional SMS Templates
Transactional SMS templates allow you to send non-OTP messages for specific operational use cases. All templates must be manually approved before use.
Transactional SMS is not OTP
OTP messages use a fixed system template. Transactional SMS is a separate feature for sending approved operational messages like order confirmations or appointment reminders.
Allowed use cases
Order confirmations
Order placed, shipped, delivered
Payment receipts
Payment received, refund processed
Appointment reminders
Upcoming appointments, schedule changes
Account alerts
Security alerts, password changes
Template restrictions
- No links or URLs
- No promotional content or offers
- No free-text composition at runtime
- No bulk campaigns
Template approval process
- Submit template via Dashboard → Templates → Create
- Include template name, content, and use case description
- Review takes 1-2 business days
- Approved templates receive a
template_id
Example template submission
Your order {{order_id}} has been shipped.
Expected delivery: {{delivery_date}}.
Thank you for shopping with {{brand_name}}.
Sending a transactional SMS
/v1/sms/send
curl -X POST https://api.nepalotp.com/v1/sms/send \
-H "Authorization: Bearer notp_live_sk_1a2b3c4d5e6f7g8h9i0j" \
-H "Content-Type: application/json" \
-d '{
"phone": "9841234567",
"template_id": "tpl_order_shipped",
"variables": {
"order_id": "ORD-2024-0847",
"delivery_date": "Jan 18, 2024",
"brand_name": "ShopNP"
}
}'
Webhooks
Webhooks let you receive real-time delivery and verification updates for OTP and SMS without polling. Configure a webhook URL per app and we will POST signed events whenever activity occurs.
Enable webhooks
- Open Dashboard → Apps → Manage
- Enable Webhooks and provide a
webhook_url - Set a
webhook_secretto sign requests (recommended) - Return a
2xxresponse quickly to acknowledge delivery
Events
otp.sent
OTP delivered to provider successfully.
otp.failed
OTP delivery failed.
otp.verified
OTP verified by the end user.
sms.sent
Transactional SMS delivered to provider.
sms.failed
Transactional SMS delivery failed.
Example payload
{
"id": "evt_9d8a7c6b5a4f3e2d",
"event": "otp.sent",
"created_at": "2026-01-04T16:42:00+05:45",
"app": {
"app_id": "app_k2Y9s0XrT3aB",
"name": "Demo App",
"environment": "sandbox"
},
"data": {
"otp_id": "otp_8f9a7b6c5d4e3f2a",
"phone": "+9779812345678",
"reference": "login-123",
"status": "sent",
"is_sandbox": true,
"cost": "0.0000",
"expires_at": "2026-01-04T16:47:00+05:45",
"sent_at": "2026-01-04T16:42:00+05:45"
}
}
Signature verification
We sign webhook requests when a webhook_secret is set. Verify the
signature using the raw request body and the X-Npot-Signature header.
$signature = $_SERVER['HTTP_X_NPOT_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_NPOT_TIMESTAMP'] ?? '';
$payload = file_get_contents('php://input');
$expected = hash_hmac('sha256', $timestamp . '.' . $payload, $webhookSecret);
$header = "t={$timestamp},v1={$expected}";
if (! hash_equals($header, $signature)) {
http_response_code(400);
exit('Invalid signature');
}
Retries & timeouts
We retry failed webhooks up to 3 times with backoff. Your endpoint should respond within 10
seconds and return a 2xx status. On cPanel, you can keep delivery synchronous by using the
sync queue driver or run a
cron-based queue worker for higher throughput.
Template Variables & Validation
Template variables allow dynamic content insertion within approved templates. Variables are validated at runtime to ensure message integrity.
Variable syntax
Variables use double curly brace syntax: {{variable_name}}
Naming rules
- Lowercase letters, numbers, and underscores only
- Must start with a letter
- Maximum 32 characters
Value rules
| Constraint | Limit |
|---|---|
| Maximum value length | 128 characters |
| Allowed characters | Alphanumeric, spaces, common punctuation |
| Disallowed | URLs, special characters, control characters |
Runtime enforcement
When sending a transactional SMS, the API validates that:
- All required variables are provided
- No extra variables are included
- Values conform to length and character rules
Validation error examples
{
"success": false,
"error": {
"code": "MISSING_VARIABLE",
"message": "Required variable 'order_id' is missing"
}
}
{
"success": false,
"error": {
"code": "UNKNOWN_VARIABLE",
"message": "Variable 'promo_code' is not defined in template"
}
}
Rate Limits & Abuse Protection
Rate limits protect the platform and end users from abuse. Limits are enforced at multiple levels.
OTP rate limits
| Scope | Limit | Window |
|---|---|---|
| Per phone number | 5 OTPs | 1 hour |
| Per phone number | 10 OTPs | 24 hours |
| Per API key | 100 requests | 1 minute |
| Per API key | 10,000 requests | 24 hours |
Transactional SMS limits
| Scope | Limit | Window |
|---|---|---|
| Per phone per template | 3 messages | 1 hour |
| Per API key | 1,000 messages | 24 hours |
Why limits exist
Rate limits prevent SMS bombing attacks against individual phone numbers, protect carrier relationships, and ensure fair usage across all customers.
Rate limit response
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many OTP requests for this phone number",
"retry_after": 3600
}
}
Pricing & Billing
NepalOTP uses a prepaid wallet model. You add funds to your account and usage is deducted in real time.
Prepaid wallet
Add funds via the dashboard using supported payment methods. Your wallet balance is displayed on the dashboard and available via API.
Billing behavior
| Message type | Sandbox | Live |
|---|---|---|
| OTP SMS | No charge | Deducted from wallet |
| Transactional SMS | No charge | Deducted from wallet |
No postpaid usage
API requests fail when wallet balance is insufficient. There is no credit or postpaid billing. Monitor your balance and set up low-balance alerts in the dashboard.
Errors & Status Codes
All API errors return a consistent structure with an error code and human-readable message.
Error response structure
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human readable description"
}
}
Error code reference
INVALID_PHONE_FORMAT
Phone number is not a valid 10-digit Nepali number.
INVALID_OTP
The submitted OTP code is incorrect.
OTP_EXPIRED
The OTP has expired. Request a new one.
MAX_ATTEMPTS_EXCEEDED
Too many failed verification attempts. OTP is invalidated.
INVALID_API_KEY
API key is missing, malformed, or revoked.
INSUFFICIENT_BALANCE
Wallet balance is too low to complete the request.
OTP_NOT_FOUND
The specified otp_id does not exist.
TEMPLATE_NOT_FOUND
The specified template_id does not exist or is not approved.
RATE_LIMIT_EXCEEDED
Too many requests. Check the retry_after field.
INTERNAL_ERROR
An unexpected error occurred. Contact support if persistent.
Best Practices
OTP UX recommendations
- Show a countdown timer displaying time until expiry
- Display remaining verification attempts
- Provide a "Resend OTP" button with a cooldown (30-60 seconds)
- Use auto-focus on OTP input fields
Retry and resend handling
- Implement client-side rate limiting for resend buttons
- Handle 429 errors gracefully with user-friendly messages
- Use exponential backoff for automatic retries
When to use OTP vs transactional SMS
| Use case | Recommended |
|---|---|
| Phone verification at signup | OTP |
| Two-factor authentication | OTP |
| Password reset | OTP |
| Order shipped notification | Transactional SMS |
| Appointment reminder | Transactional SMS |
What not to do
- Never log OTP codes in your application
- Never store OTPs in client-side storage
- Never expose API keys in frontend code
- Never allow unlimited OTP resends without cooldown
FAQ
Why is NepalOTP OTP-first?
OTP verification has different requirements than general messaging: strict delivery timing, abuse protection, and security guarantees. Building OTP as the core product allows us to optimize for these requirements.
Can I send marketing SMS?
No. NepalOTP does not support marketing, promotional, or bulk SMS campaigns. All messaging must be transactional and use approved templates.
Can I edit templates after approval?
No. Approved templates cannot be modified. To change a template, submit a new template for approval and update your code to use the new template_id.
How many templates can I create?
Standard accounts can have up to 20 active templates. Contact support if you need more.
How long do OTPs last?
OTPs expire after 5 minutes. This is a fixed value and cannot be configured.
Will WhatsApp OTP be supported?
WhatsApp as a delivery channel is planned for a future release. Currently, only SMS delivery is available.
Support & Contact
Contact support
For technical issues, integration questions, or account inquiries:
support@nepalotp.comWhen contacting support, include:
- Your account email or organization name
- Request ID from the API response (if applicable)
- Timestamp of the issue
- Error code and message received
- Steps to reproduce the issue