Pick the login you want
Every flow below ends with a verified phone number. They differ in how much the user has to do to get there — and in what you have to set up first. Start from the experience you want; the reference pages have the parameters.
The number of taps is the product
My returning users shouldn't have to type a code every single time.
They don't. After a user verifies once, the SDK keeps a device token; the next login presents it and the server verifies them without sending anything. No message, no code, no wait — and no cost to you for a message never sent.
- UserOpens app
- Systeminitiate()device token attached
- SystemToken matchedtrust still live
- UserSigned in
onetap_enabled for the merchant, or every login takes the full path and nothing says why.I want the OTP to fill itself in, not make the user switch apps to read it.
Send the code over WhatsApp with a zero-tap template. WhatsApp hands it straight to your app and the SDK fills the field. The user never leaves your screen and never sees the message.
- UserTaps Continue
- SystemHandshakeapp → WhatsApp
- SystemTemplate sentcode + supported_apps
- SystemBroadcastWhatsApp → your app
- UserVerified
initiate(). The app hash must be the hash of the build being tested: a debug build is not signed with your release key.// One call. The code arrives over WhatsApp, the SDK reads it,
// and it verifies without the user typing anything.
await QuickAuth.auth.initiate(
phone: '+919876543210',
channel: OtpChannel.whatsapp,
autoSubmit: true,
);
// Resend later, to the same number — no arguments, so a
// reformatted one cannot start a second transaction.
await QuickAuth.auth.resendOtp();WhatsApp is cheaper, but not everyone has it. I don't want to pick one.
Set WhatsApp as your primary channel and SMS as fallback. Every login tries WhatsApp; if the message is not delivered within your window, the same code goes out over SMS. The user gets one code either way.
- UserEnters number
- SystemWhatsApp sent
- SystemNo receiptwindow elapsed
- SystemSMS sentsame code
- UserEnters code
channel: "auto" and the merchant's configured primary decides, so changing it in the dashboard does not require an app release.DLT gives me {#alp#} three times. How is my backend supposed to know which is which?
Name them once in the dashboard, then send values by name instead of position. DLT registers what a placeholder allows, never what it means — so the meaning is yours to declare, and it lives with the template rather than in every caller.
- UserImports templateHi {#alp#}, order {#alp#}
- UserNames each slotname · orderId
- SystemSend by namevariableValues
- SystemFilled in order
curl -X POST 'https://api.quickauth.in/v1/sms/send' \
-H 'Content-Type: application/json' \
-H 'X-Client-Id: YOUR_CLIENT_ID' \
-H 'X-Client-Secret: YOUR_CLIENT_SECRET' \
-d '{
"templateId": "1707169123456789012",
"to": "+919876543210",
"senderId": "ELEFNT",
"idempotencyKey": "order-ORD-1-confirm",
"variableValues": {
"name": "Rohit",
"orderId": "ORD-1",
"deliveryDate": "Friday"
}
}'Set idempotencyKey from the event, not at random
order-ORD-1-confirm), not from a fresh UUID per attempt: a new key every time is no protection at all.Which flow costs what
Every flow above bills the same way — per delivered message, at the rate configured for that channel. OneTap is charged as an authentication on the channel it replaced: it sends nothing, but it is the auth event you are paying for, not the message.
Start here if you are new