Integrate WhatsApp OTP in minutes.
Your server sends requests to the WAppOTP API. Keep the project API key on your server and never expose it in browser JavaScript.
Integrate with Claude Code, Codex or Cursor
Paste this prompt into your AI coding agent inside your project. Set WAPPOTP_API_KEY in your.env first, and never paste the key into the chat. In the dashboard under Developers you can tailor it to your stack (Next.js, Laravel, Python, WordPress) and goal (phone verification, checkout verification or order messages).
You are integrating WAppOTP (WhatsApp OTP and messaging API) into this project.
## Context
- API base URL: https://api.wappotp.com
- Docs: https://wappotp.com/docs · OpenAPI: https://wappotp.com/openapi.json · Full reference for AI: https://wappotp.com/llms-full.txt
- Auth: Authorization: Bearer <secret API key> (keys start with wa_live_). Read it from the WAPPOTP_API_KEY environment variable.
- Phone numbers are always E.164, e.g. +639171234567.
- Detect the framework and language from this repository and follow its existing conventions (routing, env loading, HTTP client, validation, tests).
## Task
Goal: verify users' phone numbers with a WhatsApp one-time password during sign-up or login.
1. Add an endpoint that receives the user's phone number, normalises it to E.164 (for example +639171234567) and calls:
POST https://api.wappotp.com/v1/otp/send
Headers: Authorization: Bearer $WAPPOTP_API_KEY, Content-Type: application/json, Idempotency-Key: <unique per attempt>, x-whatsappotp-client-ip: <end user's IP>
Body: {"to":"+639171234567","fallback":{"sms":true}}
Response: {"verificationId":"…","status":"pending","expiresIn":300}
Store verificationId server-side (session) — never trust it from the client alone.
2. Add an endpoint that receives the 6-digit code and calls:
POST https://api.wappotp.com/v1/otp/verify
Body: {"verificationId":"…","code":"482913"}
Response: {"valid":true,"status":"verified"} — only mark the phone as verified when valid is true.
3. Build the UI: phone input → "Send code" → 6-digit code input → "Verify", with a resend button after 60 seconds and clear error messages.
4. Save the verified phone number (and the time it was verified) on the user record.
## Requirements
- Never hard-code or log the API key or webhook secret; add WAPPOTP_API_KEY (and WAPPOTP_WEBHOOK_SECRET if used) to .env.example with empty values and document them in the README.
- Call WAppOTP only from server-side code. Never expose the secret key to the browser or mobile app.
- Handle errors: 400 (invalid input — show the message), 402 (plan limit reached), 409 (idempotency key reused with a different body), 429 (rate limited — tell the user to wait). Log the x-request-id response header.
- Add rate limiting or CAPTCHA on public endpoints that trigger an OTP send.
- Add tests for the new service/endpoints with the HTTP calls mocked, and keep the existing code style.
- When done, list the files you changed, the env variables to set, and how to test it end to end.
Before writing code, briefly confirm your plan for where each piece goes in this codebase.Send yourself a real OTP
Server-side API key
Add the project key to every developer API request:
API keys are permission-scoped. For a login-only integration, grant only otp:send and otp:verify. Add message, consent, or campaign scopes only when that integration actually uses them.
Send by SMS in the Philippines
Buy SMS credits under Billing. One credit sends one SMS of up to 160 characters (longer messages use more); OTPs go through a dedicated priority route and use 2 credits per SMS. SMS works for Philippine mobile numbers (+639…) only. Credits never expire, and messages the network rejects are refunded automatically. Messages cannot start with the word "TEST".
SMS and email fallback for OTP
With fallback.sms and/or fallback.email, the code is sent by SMS (tried first) or email if no WhatsApp number is connected, or if WhatsApp has not sent it within about 45 seconds. The response's channel says where it went first. The email uses fixed wording with your project name, and verification works the same either way.
Send a saved template
Create templates in the dashboard under Templates. Every {{placeholder}}must be supplied in variables; missing ones return a 400 that lists them.
Shorten and track links
Create a short link with POST /v1/links (scope messages:send) and read its click count with GET /v1/links/{id} (scope messages:read). Leave out alias for a random 7-character code. With the add-on, links of 40 characters or more in messages, media captions and campaigns are shortened automatically (turn this off per project in the Links tab). SetshortenLinks: true to shorten every link, or false to send a message exactly as written. The same URL reuses its short link, so retries send identical text.
Creating short links needs the URL shortener add-on, which the workspace owner buys yearly under Billing. Without it, POST /v1/links and shortenLinks: true return402, and messages are sent with their links unchanged. Links you already created keep redirecting even if the add-on lapses.
Only public http(s) addresses can be shortened. Short links stop redirecting when you turn them off in the dashboard or when your project is suspended. Clicks are counted; no visitor data is stored.
Choosing the sending number
Pass connectionId to send from a specific number. Without it, the project's oldest connected number is used. Turn on sender rotation under Dashboard → Connections to spread recipients across all connected numbers instead: each recipient always hears from the same number, and volume is shared evenly.
Available API key scopes
Send and verify codes
POST the returned verificationId and the six-digit code to /v1/otp/verify. Codes expire automatically and are stored only as hashes.
Server SDK
Send normal WhatsApp messages
Use GET /v1/messages/:messageId or webhooks for asynchronous delivery status. Direct-message state progresses through QUEUED, SENDING, SENT, DELIVERED and READ when WhatsApp receipts are available; terminal failures use FAILED.
Images, videos and documents
Media must use a public HTTPS URL. The worker rejects redirects, private/reserved destinations, invalid MIME types, empty files, images larger than 16 MB, and video/document files larger than 32 MB. Documents require a filename.
Consent before campaigns
Record OPTED_IN consent using /v1/consents before creating a campaign. The API rejects recipients without active consent or recipients on the suppression list. Inbound opt-out keywords are processed automatically. Campaigns may also include one optional IMAGE, VIDEO, or DOCUMENT using the same secure public-HTTPS media pipeline as transactional media messages. A queued campaign can be stopped with POST /v1/campaigns/:id/cancel; recipients that have not started delivery are marked BLOCKED and stale queue jobs are ignored by the worker.
Signed asynchronous events
Create webhook endpoints in the dashboard. Verify the x-whatsappotp-signature header by computing HMAC-SHA256 over timestamp + "." + the exact raw request body. The message.received event is emitted only for new one-to-one inbound messages; media events include metadata/caption only and the platform does not automatically download inbound media.
The included JavaScript, PHP, and Python SDKs now provide a webhook signature verifier with a default five-minute timestamp tolerance to reject replayed requests.
Retries, idempotency and request IDs
Send Idempotency-Key on OTP sends, transactional sends, and campaign creation. Reusing a key with the same body returns the original result; changing the body returns HTTP 409.
Every API response includes x-request-id. Store it with your application logs when troubleshooting.
Dependency-free PHP client
A PHP 8.1+ cURL client is included under integrations/php. It supports OTP, verification, transactional messages, consent, campaigns, idempotency keys, client-IP forwarding and request-ID errors.
Dependency-free Python client
A Python 3.11+ standard-library client is included under integrations/python with the same server-side API coverage and retry controls.
Included WordPress plugin
The repository contains integrations/wordpress/whatsappotp. Configure the API URL and project key in WordPress settings, then use the shortcode [whatsappotp_verify].
Keep the secret in an app backend
Never place the project secret in Liquid or storefront code. Your Shopify app backend calls WAppOTP and the storefront talks only to your app backend. A server-side integration guide is included under integrations/shopify.