Getting Started

In this documentation, we will take you through a quick overview of how to use LightOTP. As you might expect, LightOTP is simple and developer-friendly, requiring minimal setup to get started.

In this guide, we will walk you through the essential steps, including how to purchase a plan and how to use API keys to start sending OTP messages.

The process is straightforward and designed to help you integrate OTP functionality into your application quickly and efficiently.


Purchase a Plan

Before sending any OTP messages, you need to subscribe to a plan.

Steps:

  1. Go to the "Subscriptions" section in your dashboard, and click "Purchase plan".
  2. Select a plan from the available, and click "Subscribe".
  3. Automated payment is currently unavailable. Click "Confirm Order", add optional notes if needed, and our team will contact you via WhatsApp or email to activate your plan.

Send OTP Messages

Steps:

  1. Go to the "API Keys" section in your dashboard, and add a new API key. Make sure the key is stored securely and not shared with anyone outside your team.
  2. Call the Send Message endpoint with the OTP code and recipient phone number.
  3. Persist the returned id so you can later look up delivery status via the Check Message Status endpoint.

Endpoint

API end point:

POSThttps://api.lightotp.com/SendMessage
ItemValue
MethodPOST
Path/SendMessage
Content-Typeapplication/json
AuthenticationX-Api-Key HTTP header
Success status200 OK

Authentication

Every request must include your API key in the X-Api-Key request header. API keys are issued per client from the LightOTP dashboard and may be assigned an optional expiration date. Requests with a missing, invalid, or expired key are rejected before any credits are consumed.

Treat the API key as a credential. Store it server-side, never expose it in mobile apps, browser code, or public repositories. If a key is compromised, revoke and rotate it from the dashboard immediately.

Headers

HeaderRequiredDescription
X-Api-KeyYesYour client API key.
Content-TypeYesMust be application/json.
Accept-LanguageNoar or en. Controls the language of error and email notifications returned by the platform (does not select the WhatsApp message language — see languageCode).

Body

Example body:

{
  "otpCode": "123456",
  "toPhoneE164": "+966551234567",
  "languageCode": "en",
  "idempotencyKey": "a3f1c2e4-9b27-4d6a-8e5f-1c2b3d4e5f60"
}
FieldTypeRequiredConstraints
otpCodestringYes1–8 characters. Letters (A–Z, a–z) and digits (0–9) only. No spaces or symbols.
toPhoneE164stringYesRecipient phone number in E.164 format (e.g., +966551234567).
languageCodestringNoBCP-47 (e.g., en, ar, en_US, ar_SA). Selects the language of the WhatsApp message. If omitted or no matching language is available, the platform falls back to your account's default language.
idempotencyKeyuuidNoOptional client-supplied UUID for safe retries. See idempotencyKey below.

otpCode — full validation rules

  1. Must be included in the request.
  2. Length must be ≤ 8 characters.
  3. Every character must be a letter or a digit. Hyphens, spaces, dots, and other punctuation are rejected.

toPhoneE164 — phone format

The number must be a valid number for its region; merely being well-formed (e.g., starting with + and the right digit count) is not enough.

Valid:

  • +966551234567
  • +201001234567
  • +14155552671

Invalid:

  • 0551234567 (no country code)
  • +999999999999 (not a valid region)

languageCode — language selection

If you provide a languageCode, the message is sent in that language. If no matching language is available, the platform falls back to your account's default language.

idempotencyKey — safe retries

When you supply an idempotencyKey, the platform stores it alongside the message it creates. If you later call this endpoint again with the same idempotencyKey under the same API key, the platform does not send a new message and does not charge your account again — it simply replays the original response, returning the same id and the same messageStatus you received the first time. This makes it safe to retry a request whose response you didn't receive (timeouts, dropped connections, transient network failures) without risking a duplicate send. If the replayed response shows messageStatus as Failed, the original message did not go through; reusing the same idempotencyKey will keep returning that failed result. To actually deliver the OTP in that case, generate a new idempotencyKey and submit the request again. Each idempotencyKey must be unique across all of your previous requests; once used, it is permanently bound to that one message and cannot be reused for a different one. Omit the field if you don't need this guarantee — the platform will accept the request without it.

Success Response — 200 OK

Example response:

{
  "id": "f5b2d2e9-2c1f-4f1d-9c89-2c41f3d9a4e2",
  "messageStatus": "Sent"
}
FieldTypeDescription
iduuidUnique LightOTP message identifier. Persist this value to look up delivery status later via CheckMessageStatus.
messageStatusstringInitial status returned by WhatsApp at the time of the call. One of: Pending, Sent, Delivered, Read, Failed, Deleted. Almost always Pending.

messageStatus lifecycle

StatusMeaning
PendingCreated locally; not yet acknowledged by WhatsApp.
SentAccepted by WhatsApp for delivery.
DeliveredDelivered to the recipient device.
ReadThe recipient opened the chat containing the message.
FailedWhatsApp rejected the send or downstream delivery failed.
DeletedMessage was deleted (administrative action).

Use Check Message Status to read the latest state of a message.

Errors

All errors are returned as JSON in the form:

Example error:

{
  "errorMessage": "InsufficientBalance"
}

Error codes

CodeHTTPMeaning
ApiKeyIsRequired400The X-Api-Key header was missing or empty.
ApiKeyNotFound404No active API key matches the provided value.
ApiKeyExpired400The API key has passed its expiration date.
OTPCodeIsRequired400otpCode was missing.
OTPCodeLengthMustBeLessThanOrEqualTo8400otpCode exceeds 8 characters.
OTPCodeMustContainOnlyLettersOrDigits400otpCode contains characters other than letters and digits.
DestinationPhoneNumberIsRequired400toPhoneE164 was missing.
InvalidphoneNumber400toPhoneE164 is not a valid E.164 phone number.
PhoneNumberCanNotBeEmpty400toPhoneE164 was empty.
InsufficientBalance400No active subscription has enough credits to cover the cost of this message for the destination country.
SenderNumberNotFound404No sender number is assigned to your account.
TemplateNotFound404No matching language could be found for this request.
Duplicate-send cooldown message400The duplicate-send cooldown is active for this destination. See duplicate-send protection.
WhatsApp service error4xxWhatsApp service rejected the send. The errorMessage is the message returned by WhatsApp; the HTTP status mirrors WhatsApp's response status.
InternalServerError500An unexpected error occurred. Please try again later, and if the issue persists, contact support.

Billing & credits

Each successful send debits one or more credits from your most-recently created active subscription that has sufficient balance.

  • Default cost: 1 credit per message.
  • Credits are deducted based on the destination country according to our international pricing table. Countries not listed default to 1 credit per message.
  • Failed messages are not charged from your balance, but are still recorded for tracking.
  • When your subscription's credits are fully consumed, it is automatically marked as expired.
  • We send you an email when your subscription's balance is running low.

Duplicate-send protection

To prevent repeatedly sending the same OTP to a recipient, the platform applies a wait time per recipient phone number, which grows the more you resend to the same number:

  • Initial wait time: 30 seconds after a successful send.
  • Each additional send within the rolling 6-hour window doubles the wait time: send #1 → 30s, send #2 → 60s, send #3 → 120s, send #4 → 240s, and so on.
  • The wait time is capped at 6 hours.
  • The wait time resets after 6 hours with no non-failed sends to that number.
  • Failed messages do not count toward the wait time.

While the wait time is active, the API responds with 400 Bad Request and an errorMessage of the form: "You can't send another message to the same number yet. Please wait HH:mm:ss and try again."

Rate limiting

The platform applies per-IP request limits to prevent abuse. When the limit is exceeded, the API returns 429 Too Many Requests.

Example (cURL)

cURL:

curl -X POST "https://api.lightotp.com/SendMessage" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Accept-Language: en" \
  -d '{
    "otpCode": "482913",
    "toPhoneE164": "+966551234567",
    "languageCode": "en",
    "idempotencyKey": "a3f1c2e4-9b27-4d6a-8e5f-1c2b3d4e5f60"
  }'

Check Message Status

After a successful send, use this endpoint to read the latest state of a message (including failure details if it failed).

API end point:

POSThttps://api.lightotp.com/CheckMessageStatus
ItemValue
MethodPOST
Path/CheckMessageStatus
Content-Typeapplication/json
AuthenticationX-Api-Key HTTP header
Success status200 OK

Request Body

Example body:

{
  "id": "f5b2d2e9-2c1f-4f1d-9c89-2c41f3d9a4e2"
}

Success Response — 200 OK

Example response:

{
  "id": "f5b2d2e9-2c1f-4f1d-9c89-2c41f3d9a4e2",
  "messageStatus": "Delivered",
  "toPhoneE164": "+966551234567",
  "createdAt": "2026-05-14T09:13:42.812Z",
  "failureCode": null,
  "failureTitle": null,
  "failureReason": null,
  "failedAt": null
}
FieldTypeDescription
iduuidThe LightOTP message identifier.
messageStatusstringCurrent status: Pending, Sent, Delivered, Read, Failed, or Deleted.
toPhoneE164stringThe destination phone number the message was sent to.
createdAtdatetimeUTC timestamp when the message was created.
failureCodeinteger | nullWhatsApp Graph API error code, if available.
failureTitlestring | nullWhatsApp error type / title.
failureReasonstring | nullHuman-readable failure reason.
failedAtdatetime | nullUTC timestamp when the failure was recorded.

Failure fields

When the message has failed, the failure fields are populated:

FieldTypeDescription
failureCodeinteger | nullWhatsApp Graph API error code, if available.
failureTitlestring | nullWhatsApp error type / title.
failureReasonstring | nullHuman-readable failure reason.
failedAtdatetime | nullUTC timestamp when the failure was recorded.

Errors

CodeHTTPMeaning
ApiKeyIsRequired400The X-Api-Key header was missing.
ApiKeyNotFound404No API key matches the provided value.
ApiKeyExpired400The API key has expired.
MessageNotFound404No message with that id exists for your client.

Example (cURL)

cURL:

curl -X POST "https://api.lightotp.com/CheckMessageStatus" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{ "id": "f5b2d2e9-2c1f-4f1d-9c89-2c41f3d9a4e2" }'

Try it yourself

Head to your dashboard to generate an API key and send your first OTP, or contact us if you need help getting started.