We do not generate, store, expire or check the code. That stays yours.
This endpoint delivers a code you already own, which is the honest description
of what it does.
Body
string
required
The number to send it to. E.164, with or without the
+.string
required
The code you generated. Sixteen characters at most — beyond that it is almost
always the wrong field, and a JWT in front of a customer as “your verification
code” is worse than a refusal.
string
Which code template to use. Only needed when you have more than one approved —
a second language, say. With one, we use it.
string
Narrows the choice when you keep the same template name in several languages.
Response
Retry on
429 and 502, never on a 4xx that is not one of them. A
400 means the request will fail identically however many times you send it;
a 502 means the moment was bad and the next one may not be.A 409 rather than a guess. With two approved templates and no
template,
choosing for you means choosing which language somebody reads their login code
in. That is not a decision to make quietly, and it is one line of code to make
explicit.If you have one name approved in several languages, template narrows
nothing — the response says so and asks for language instead.Not sending the same code twice
Two different things go wrong here and each needs its own answer.A retry of the same request
Send anIdempotency-Key.
idempotencyKey works too; the header wins if you send both.
Keys are honoured for 24 hours, which is long enough for any retry worth
making. After that the same string is a new request and sends again — so if you
reuse a stable key like login-8891, be aware that tomorrow’s login for that
same session id will go out rather than replay.
If a request carrying that key is still in flight, the second one gets
409 rather than a second code. Retry after a moment and it will have an
answer to give you.The same person asking again
An idempotency key cannot help with this one, and it is the failure OTP endpoints actually have. Five different requests for one number — somebody tapping Resend, or a login screen looping — are five different requests, and each is legitimately asking for a new code. So there is a second limit: five codes to one number in ten minutes. Past it,429 with a Retry-After saying when the window clears.
This is Twilio Verify’s number, not one we invented — the same five-in-ten
that their
60203 refuses at. It is enough for a person who genuinely mistyped
their number twice, and not enough to be worth abusing.Or through the general endpoint
POST /api/v1/messages still works — an authentication template is a template —
and you would name it yourself:
Pass the code once, in
variables. Do not also put it in
buttonVariables — we copy it into the button for you. See below for why that
matters.You do not write an authentication template
This surprises people, and it is a mercy rather than a restriction. Meta writes the body:394812 is your verification code. For your security, do not share this code. Expires in 10 minutes.You cannot change that wording, add your brand to it, or explain anything in it. OTP is the one message where wording is a liability, and Meta has already argued with every phishing pattern there is. Templates are created, submitted and deleted in the console — see One-time codes. There is no API for managing them, deliberately: a template is reviewed by Meta and lives for months, so it is not something a deploy should be creating on the fly. What you do choose, on the One-Time Codes page in the console:
on / off
Appends “For your security, do not share this code.” On by default.
1–90 minutes
Shown to the customer in their own language, by Meta. Ten minutes by default:
long enough to find your phone, short enough that a screenshot in a group chat
is worthless by the time it matters.
Copy code / One-tap
How the code gets out of WhatsApp and into your app.
- Copy code — they tap, then paste. Works everywhere, needs no integration. This is the default and the right answer for almost everyone.
- One-tap autofill — hands the code straight to your Android app. Needs your app’s package name and signing hash, which the form asks for.
The thing everybody gets wrong
A copy-code button does not read the code out of the message. Meta treats the body and the button as separate components, and the code has to be supplied to both. Send it only in the body and the button copies nothing — the customer taps it, gets an empty clipboard, and blames your app. We do this for you: when the template is an authentication one with a code button, the value you put invariables[0] is sent to the button as well.