Send OTP
Send a WhatsApp verification code with stricter OTP validation and rate limits.
When to use OTP
Use the OTP endpoint when the message contains a short verification code and should be subject to OTP-specific limits. The request can provide a code, or the backend can generate one.
OTP messages are still queued messages. Store the returned `message_id` if your application needs to trace delivery status later.
`code` is optional everywhere. If your application sends it, it must be 4 to 10 digits. If omitted, the API generates the code and returns it as `otp_code`.
Response includes the code
The current API response includes `otp_code` so your backend can verify the user-entered code. Keep that value server-side.
Endpoint reference
The endpoint requires an API key with the `otp` role. Free Sandbox uses a managed sender and omits `sender_id`; paid connected-sender mode includes an assigned sender ID.
/api/otp/sendGenerate or send a caller-provided OTP code.
| Parameter | Type | Description |
|---|---|---|
x-api-keyrequired | header | API key with the `otp` role. |
sender_id | uuid | Paid mode only: an assigned connected sender. Omit for managed routing. |
receiver_numberrequired | string | Destination phone number. |
code | string | Optional 4-10 digit code. If omitted, the server generates one. |
app_name | string | Application name shown in the OTP message, max 80 characters. |
ttl_minutes | integer | Positive integer up to 60.Default: 10 |
template | string | Optional custom template, max 512 characters. |
idempotency_key | string | Duplicate protection key, max 160 characters. |
{
"receiver_number": "212612345678",
"app_name": "My Store",
"ttl_minutes": 10,
"source_system": "login",
"source_reference": "user_123"
}{
"message_id": "6d92aa83-8fb4-40f8-8ef2-83c2b8f0319f",
"status": "queued",
"duplicate": false,
"otp_code": "482913",
"expires_in_minutes": 10
}Generated vs application codes
Use backend-generated OTP when you want HookMessage to create the code and return it to your server. Store `otp_code` in your own verification attempt record and compare it with the user-entered value.
Use application-generated OTP when your auth system already creates and stores codes. Pass that code in the request and still keep the returned `message_id` for delivery tracing.
- Generated flow: omit `code`, store returned `otp_code`, expire it after `expires_in_minutes`.
- Application flow: send `code`, store the same code in your auth system, and never expose the API key to the browser.
- Verification: your backend compares the user-entered code with the code stored for that login/signup attempt.
- Security: do not log OTP codes in application logs or analytics events.
{
"receiver_number": "212612345678",
"code": "482913",
"app_name": "My Store",
"ttl_minutes": 10,
"source_system": "login",
"source_reference": "attempt_123"
}Node.js example
Keep the API key in the server environment and return only your own application result to the browser.
const response = await fetch(`${process.env.HOOKMESSAGE_BASE_URL}/api/otp/send`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": process.env.HOOKMESSAGE_OTP_KEY!,
},
body: JSON.stringify({
...(process.env.HOOKMESSAGE_SENDER_ID
? { sender_id: process.env.HOOKMESSAGE_SENDER_ID }
: {}),
receiver_number: phoneNumber,
app_name: "My Store",
ttl_minutes: 10,
}),
});
if (!response.ok) {
throw new Error("Could not send verification code");
}
const data = await response.json();Need help?
Use Book Integration Help if you want setup help for OTP or automated customer messaging.
Continue building
Next steps
Message status and history
Use message IDs to trace status, delivery attempts, timestamps, and failure reasons.
SDK setup and examples
Choose a sender mode, configure server-only secrets, then call the same REST contract from your language.
Webhooks and automation
Register webhook endpoints per API client and subscribe to the events your system needs.
Troubleshooting
Use this page when a new integration does not send, status stays queued, or webhook verification fails.