Skip to guide
WhatsOS

CUSTOMER-GENERATED CODES

Send your own OTP

Your software generates and verifies the code. WhatsOS delivers it through your approved WhatsApp authentication template.

ResponsibilityWhatsOS-generated OTPSend your own OTP
Generate, expire and verifyWhatsOSYour software
WhatsApp deliveryWhatsOSWhatsOS
Submit endpointPOST /verificationsPOST /deliveries
ResultVerification ID and verification/delivery statusDelivery ID and delivery status

Choose per request in the same OTP project. Your existing key works with otp:send and otp:read. Keep the full secret in your backend or trusted device gateway. Signed-in workspace tests can use the saved key without entering its secret.

Deliver the code from your software

POSThttps://whatsos.com/api/v1/otp/deliveries

Send a JSON body with the recipient's international number, your six-digit code as a string, and its absolute expiry. Leading zeros are preserved. The configured template and language are reused; the code is inserted into both the message and Copy Code button.

Authorization: Bearer YOUR_EXISTING_API_KEY
Content-Type: application/json
Idempotency-Key: login-session-001-delivery-1

{
  "to": "+971501234567",
  "code": "004281",
  "expires_at": "REPLACE_WITH_YOUR_CODE_EXPIRY_ISO_TIMESTAMP",
  "purpose": "login",
  "reference": "login-session-001"
}

expires_at is an ISO 8601 timestamp with timezone, such as the value returned by JavaScript's new Date(yourCodeExpiry).toISOString(). At first submission it must be 30 seconds to five minutes ahead. purpose defaults to login and must be enabled for your project. reference is optional, up to 120 characters. locale is optional; it defaults to your configured template language and must match it if supplied.

The HTTP 202 response contains an odl_… delivery ID, mode: "delivery_only", status: "queued", masked recipient and timestamps. It contains no code, verification ID or verification result.

Node.js example

import { WhatsosOtp } from './whatsos-otp.mjs';
const client = new WhatsosOtp({
  origin: 'https://whatsos.com',
  apiKey: process.env.WHATSOS_OTP_KEY
});
// Load this saved operation from your application.
// Save the code, expiry and idempotency key before the first call.
const operation = await yourApplication.loadOtpDeliveryOperation();
const delivery = await client.deliver({
  to: operation.phone,
  code: operation.code,
  expires_at: operation.expiresAt,
  purpose: 'login',
  reference: operation.sessionId
}, operation.idempotencyKey);
const status = await client.getDelivery(delivery.delivery_id);

Use your existing code generator, verification, attempt limits and action-session binding. Do not approve a login or transaction just because a message was delivered.

Read delivery status or receive callbacks

GET/api/v1/otp/deliveries/{delivery_id}otp:read
StatusMeaning
queued / submittingWaiting for submission / submitting to the provider.
acceptedWhatsApp accepted the message request.
sent / delivered / readProvider delivery progress. These do not verify the OTP.
failedDefinitive submission rejection or provider delivery failure. See error_code.
unknownAcceptance could not be confirmed. Do not blindly send again.
expiredStopped before submission because too little time remained.

New failures may include provider_error with the failure stage (template_check, submission or delivery), numeric Meta code/subcode and http_status when available. These safe fields appear in delivery details, history and callbacks. Raw provider messages, recipient data and codes are excluded. Older failures may have no details; they cannot be reconstructed.

In Delivery callbacks, subscribe your HTTPS endpoint to otp.delivery.accepted, otp.delivery.sent, otp.delivery.delivered, otp.delivery.read, otp.delivery.failed, otp.delivery.unknown or otp.delivery.expired. Existing destinations need these subscriptions added explicitly.

Events include id, type, schema_version: "1", project_id, environment, delivery_id, mode, current status, reference and timestamps. They never include your OTP. Treat callbacks as at least once, deduplicate by event ID and handle out-of-order delivery; GET returns the latest state. Use the existing signed webhook verification instructions and SDK verifyWebhook helper on the exact raw body.

Test projects simulate an accepted and delivered result without contacting WhatsApp. Your supplied test code is not replaced with the managed test code 123456.

Duplicate protection, expiry and errors

  • Persist one unique Idempotency-Key (8–128 letters, digits, underscores, dots, colons or hyphens) for each intended delivery. Retry an uncertain HTTP request with the identical body and key, including its original expiry. Accepted request outcomes are retained for 24 hours. Read the delivery ID for current status.
  • Changing the body under the same key returns 409 IDEMPOTENCY_CONFLICT. A new intended send needs a new key. The reference field alone does not deduplicate messages.
  • WhatsOS stops queued work and retries when less than 30 seconds remain before expiry. Temporary authentication-template lookup failures (including timeouts, HTTP 429 and 5xx) and explicit submission rate limits may be retried within a maximum of three worker attempts while sufficient time remains. Retry-After is respected. Ambiguous outcomes are not automatically resent.
  • Submission deadlines cannot recall a message already accepted by WhatsApp or guarantee it reaches the recipient before expiry. Your software must reject expired codes.
  • Match your code lifetime to your approved template wording. Golf Superstore's trans_otp, en_GB template states five minutes; its software should use a matching five-minute code lifetime.
  • For 400, correct the named field or deadline. For 401/403, check the existing key's scopes, project, subscription and sender readiness. For 429, respect Retry-After. A new code needs a fresh delivery request; delivery IDs have no verify, cancel or resend endpoint.

Recipient and project limits are shared with managed OTP, including the 30-second recipient/purpose cooldown. Do not put OTP codes or secrets into references or your request logs.

Setup, usage and storage

Live delivery requires an active project, OTP entitlement, approved authentication template, connected sender and delivery callback readiness. Both OTP methods use the same subscription and OTP quota or balance. Active annual licences have no WhatsOS per-OTP fee; Meta charges remain separate.

Usage is reserved once when queued and settled once when the provider accepts submission. Definitive rejection before acceptance and queued expiry release the reservation. A failure after acceptance does not reverse the acceptance charge. Unknown outcomes retain reservations during reconciliation; unresolved ones release after 24 hours beyond expiry. Test deliveries are free.

The code is encrypted temporarily while queued and removed after submission becomes final, uncertain, or expired. API responses, history and callbacks omit it. Recipient hashes, masked numbers and references are removed from delivery records after 30 days; your software owns verification records and retention.

POS invoice links

Continue using POST /api/v1/messaging/invoices for invoice URLs generated by your POS or billing software. It requires messaging permission, recorded consent, a suitable approved utility template and its own idempotency key. Your software creates and hosts the invoice and controls URL access. See bills, receipts and invoice setup.