Skip to main content
Torpedo Verify sends a one-time code to a phone number over WhatsApp and checks the code your user types back. Torpedo generates the code, sends it from an approved WhatsApp authentication template, counts wrong guesses, and expires the code for you. If WhatsApp can’t deliver the message, Verify can fall back to SMS. The flow takes two calls:
1

Start a verification

POST /api/v1/verify with the user’s phone number. Torpedo sends the code and returns a verification id.
2

Check the code

POST /api/v1/verify/{id}/check with the code the user typed. valid: true means the number is verified.

Requirements

  • A WhatsApp balance in your workspace. Top it up from WhatsApp → Buy credits in the dashboard. See Pricing and balance.
  • For SMS fallback: SMS credits, and a Mozambican mobile number as the recipient.
You don’t need your own WhatsApp Business account. By default, codes go out from Torpedo’s shared WhatsApp sender with an approved authentication template. You can also connect your own number in the dashboard; see Templates and your own number.

Start a verification

The response is 202 Accepted. The code is sent in the background:

Request fields

Pass an Idempotency-Key header to make retries safe. A repeated request with the same key returns the original verification instead of sending a new code.

One pending verification per number

A number can only have one pending verification at a time. Starting another one returns 409 with the existing ID in the message:
Resend the code on the existing verification, or cancel it first. An expired verification doesn’t block a new one.

Check the code

A check always returns 200 while the verification is pending:
By default a verification allows 5 guesses. You can lower this to 3 or 4 in WhatsApp → Settings. Checking a verification that has already ended returns 410 Gone:
Torpedo stores only a hash of the code. Once a verification ends, the code is deleted and can’t be recovered.

Code lifetime and verification statuses

A code expires after the time set by the template, which is 5 minutes for Torpedo’s shared templates. expiresAt on the verification tells you exactly when.
Each send is recorded in attempts, with its own status (queued, sent, delivered or failed) and a trigger of initial, resend or fallback.

Resend the code

If the message didn’t arrive, send the same code again on the same verification:
channel is optional. Omit it to resend on the verification’s channel, or set it to switch, for example from WhatsApp to SMS. The response is 202 with the updated verification.
  • You can resend once 30 seconds after the last send. resendAvailableAt tells you when.
  • A verification can be sent at most 5 times in total, including the first send and any fallback.
Both limits return 429 with a Retry-After header (PREMATURE_RETRY or MAX_SEND_ATTEMPTS). Resending doesn’t extend expiresAt.

SMS fallback

With "fallback": "sms", Torpedo sends the same code by SMS when the WhatsApp message fails, for example because the number isn’t on WhatsApp. The fallback is only triggered by a failure, never by a timer, and happens at most once per verification.
Fallback is off by default. Turn it on for every verification in WhatsApp → Settings, or per request with fallback. It needs SMS credits and only reaches Mozambican mobile numbers. The SMS is sent from your default sender ID, or the one chosen in Verify settings. When a fallback is sent, you receive a verification.fallback webhook.

SMS autofill

Phones can fill in a code from an SMS automatically when the message is formatted for it. Set these in WhatsApp → Settings and Torpedo formats every Verify SMS to match: With both set, a Portuguese SMS looks like this:
The web OTP domain must be the domain of the page asking for the code. iOS suggests codes from SMS without any setup.

Bring your own code

If your system already generates codes, pass yours in code. Torpedo delivers it, and you can either check it with /check as usual, or check it yourself and tell Torpedo the result:
Reporting marks the verification approved so your conversion stats stay accurate. Only verifications started with your own code can be reported. Starting a verification with your own code while another one is pending for the same number cancels the old one instead of returning 409, since your system has already replaced the old code.

Cancel a verification

The verification becomes canceled and its code stops working. Cancel when the user abandons the flow or changes their number, so you can start a fresh verification right away.

Get and list verifications

The list accepts status, channel, to, limit and cursor, and returns { data, meta } with the same cursor pagination as the other list endpoints.

Putting it together

A typical sign-up or login screen:
  1. Start. When the user submits their number, call POST /api/v1/verify from your server with their number, your clientRef and their ip. Store the returned id in their session. Never send the code or the verification to the browser.
  2. Ask for the code. Show a code input. Disable the “Send again” button until resendAvailableAt, then call /resend when the user presses it. Offer “Send by SMS instead” as a resend with "channel": "sms" for Mozambican numbers.
  3. Check. Call /check with what the user typed. On valid: true, mark the number as verified. On valid: false, show how many attemptsRemaining are left.
  4. Handle the ends.
    • 409 VERIFICATION_PENDING on start: the user already has a code on its way. Keep the existing verification and show the code input, or cancel it and start again if they changed numbers.
    • 410 on check or resend: the verification ended (expired, too many guesses or canceled). Ask the user to request a new code.
    • 429: wait for Retry-After before trying again, and tell the user how long.

Webhooks

Subscribe a webhook to these events to follow a verification without polling: See the payload fields for each event.

Testing

Create a test API key in the dashboard to try Verify without paying for messages. With a test key, to must be either your own verified phone number or one of these sandbox numbers: Sandbox numbers send nothing and are free. The code is always 000000, and all webhooks fire as they would for a real number. Each workspace gets 20 test verifications, sandbox ones included. After that, a test key can still use sandbox numbers, but sending to your own phone number needs a live key.

Rate limits

To stop attackers from pumping messages to a number (SMS pumping and toll fraud), Verify limits how often a number can receive codes, on top of the API rate limit:
  • At most 5 starts per hour per number by default (configurable from 1 to 20 in WhatsApp → Settings), and twice that per day.
  • At most 3 starts in 10 minutes per number, whatever your setting.
  • At most 20 starts in 10 minutes per end-user IP, when you pass ip.
  • At most 15 wrong guesses per hour per number, across all its verifications.
Going over a limit returns 429 with code RATE_LIMITED and a Retry-After header. You can also restrict which countries can receive codes in WhatsApp → Settings. A number from a country that isn’t allowed returns 422 with code COUNTRY_NOT_ALLOWED.

Templates and your own number

WhatsApp only allows codes to be sent with an approved authentication template. Torpedo provides approved templates in Portuguese and English on its shared sender, so you can start right away. To send codes from your own business number and name, connect your number in WhatsApp in the dashboard (WhatsApp → Number) and create authentication templates under WhatsApp → Templates. Once your number is connected and a template for the verification’s locale is approved, Verify uses it instead of the shared sender. Pass template to pick a specific approved template by name. If no approved template matches, the start returns 422 with code TEMPLATE_UNAVAILABLE.

Pricing and balance

WhatsApp messages are charged from your workspace’s WhatsApp balance, which is separate from SMS credits. The price depends on the recipient’s country and follows Meta’s rate card. See the current rates on the pricing page, or fetch them without an API key:
The response lists a price per country (values below are only an example):
  • The balance is held in MZN or USD, chosen on your first top-up. Top up with M-Pesa or a card from WhatsApp → Buy credits.
  • Each send is charged when the verification starts or the code is resent. A WhatsApp message that fails is refunded, since WhatsApp only bills delivered authentication messages.
  • An SMS fallback or SMS-channel verification uses one SMS credit.
  • Torpedo emails you when your WhatsApp balance runs low. Set the threshold in WhatsApp → Settings.
To read your balance from code, call GET /api/v1/workspace/usage with your API key:
data.usage.whatsappBalance is the balance as a decimal string, for example "412.5000", and data.usage.whatsappBalanceCurrency is MZN, USD, or null before your first top-up. When the balance can’t cover a message, the start returns 402:

Errors