We are making changes to the WhatsApp Business Platform pricing model. See Pricing Updates on the WhatsApp Business Platform.
The OTP Android SDK is in beta and features a simplified workflow for implementing one-tap and zero-tap authentication templates. You can learn how to use it below.
Zero-tap authentication templates allow your users to receive one-time passwords or codes via WhatsApp without having to leave your app.
When a user in your app requests a password or code and you deliver it using a zero-tap authentication template, the WhatsApp client simply broadcasts the included password or code and your app can capture it immediately with a broadcast receiver.
From your user's perspective, they request a password or code in your app and it appears in your app automatically. If your app user happens to check the message in the WhatsApp client, they will only see a message displaying the default fixed text: < code > is your verification code.
Like one-tap autofill button authentication templates, when the WhatsApp client receives the template message containing the user's password or code, we perform a series of eligibility checks. If the message fails this check and we are unable to broadcast the password or code, the message will display either a one-tap autofill button or a copy code button. For this reason, when you create a zero-tap authentication template, you must include a one-tap autofill and copy code button in your post body payload, even if the user may never see one of these buttons.
Zero-tap is only supported on Android. If you send a zero-tap authentication template to a WhatsApp user who is using a non-Android device, the WhatsApp client will display a copy code button instead.
URLs, media, and emojis are not supported.
Here are some examples that make it clear to an app user that their code will automatically appear in the app.
Use the WhatsApp Business Account > Message Templates endpoint to create a zero-tap authentication template.
POST /<WHATSAPP_BUSINESS_ACCOUNT_ID>/message_templates
{ "name": "<TEMPLATE_NAME>", "language": "<TEMPLATE_LANGUAGE>", "category": "authentication", "message_send_ttl_seconds": <TIME_TO_LIVE>, // Optional "components": [ { "type": "body", "add_security_recommendation": <SECURITY_RECOMMENDATION> // Optional }, { "type": "footer", "code_expiration_minutes": <CODE_EXPIRATION> // Optional }, { "type": "buttons", "buttons": [ { "type": "otp", "otp_type": "zero_tap", "text": "<CODY_CODE_BUTTON_TEXT>", // Optional "autofill_text": "<AUTOFILL_BUTTON_TEXT>", // Optional "zero_tap_terms_accepted": <TERMS_ACCEPTED>, "supported_apps": [ { "package_name": "<PACKAGE_NAME>", "signature_hash": "<SIGNATURE_HASH>" } ] } ] } ] }
Note that in your template creation request the button type is designated as otp
, but upon creation the button type will be set to url
. You can confirm this by performing a GET request on a newly created authentication template and analyzing its components.
Placeholder | Description | Example Value |
---|---|---|
String | Optional. One-tap autofill button label text. If omitted, the autofill text will default to a pre-set value, localized to the template's language. For example, Autofill for English (US). Maximum 25 characters. |
|
String | Optional. Copy code button label text. If the message fails the eligibility check and displays a copy code button, the button will use this text label. If omitted, and the message fails the eligibility check and displays a copy code button, the text will default to a pre-set value localized to the template's language. For example, Maximum 25 characters. |
|
Integer | Optional. Indicates the number of minutes the password or code is valid. If included, the code expiration warning and this value will be displayed in the delivered message. If the message fails the eligibility check and displays a one-tap autofill button, the button will be disabled in the delivered message the indicated number of minutes from when the message was sent. If omitted, the code expiration warning will not be displayed in the delivered message. If the message fails the eligibility check and displays a one-tap autofill button, the button will be disabled 10 minutes from when the message was sent. Minimum 1, maximum 90. |
|
String | Required. Your Android app's package name. The string must have at least two segments (one or more dots), and each segment must start with a letter. All characters must be alphanumeric or an underscore ( If using Graph API version 20.0 or older, you can define your app's package name outside of the Maximum 224 characters. |
|
Boolean | Optional. Set to |
|
String | Required. Your app signing key hash. See App Signing Key Hash below. All characters must be either alphanumeric, If using Graph API version 20.0 or older, you can define your app's signature hash outside of the Must be exactly 11 characters. |
|
String | Required. Template language and locale code. |
|
String | Required. Template name. Maximum 512 characters. |
|
Boolean | Required. Set to If set to |
|
Integer | Optional. Authentication message time-to-live value, in seconds. See Time-To-Live. |
|
curl 'https://graph.facebook.com/v21.0
/102290129340398/message_templates' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer EAAJB...' \
-d '
{
"name": "zero_tap_auth_template",
"language": "en_US",
"category": "authentication",
"message_send_ttl_seconds": 60,
"components": [
{
"type": "body",
"add_security_recommendation": true
},
{
"type": "footer",
"code_expiration_minutes": 5
},
{
"type": "buttons",
"buttons": [
{
"type": "otp",
"otp_type": "zero_tap",
"text": "Copy Code",
"autofill_text": "Autofill",
"zero_tap_terms_accepted": true,
"supported_apps": [
{
"package_name": "com.example.luckyshrub",
"signature_hash": "K8a/AINcGX7"
}
]
}
]
}
]
}'
{ "id": "594425479261596", "status": "PENDING", "category": "AUTHENTICATION" }
You must include your app signing key hash in your post body.
To calculate your hash, follow Google's instructions for computing your app's hash string.
Alternatively, if you follow Google's instructions and download your app signing key certificate (step 1), you can use your certificate with the sms_retriever_hash_v9.sh shell script to compute the hash. For example:
./sms_retriever_hash_v9.sh --package "com.example.myapplication" --keystore ~/.android/debug.keystore
The supported_apps
array allows you define pairs of app package names and signing key hashes for up to 5 apps. This can be useful if you have different app builds and want each of them to be able to initiate the handshake:
"buttons": [ { "type": "otp", ... "supported_apps": [ { "package_name": "<PACKAGE_NAME_1>", "signature_hash": "<SIGNATURE_HASH_1>" }, { "package_name": "<PACKAGE_NAME_2>", "signature_hash": "<SIGNATURE_HASH_2>" }, ... ] } ]
Alternatively, if you have only a single app, you can define the app's package name and signing key hash as buttons object properties, but this is not recommened as we will stop supporting this method at a future date:
"buttons": [ { "type": "otp", ... "package_name": "<PACKAGE_NAME>", "signature_hash": "<SIGNATURE_HASH>" } ]
You must signal to the WhatsApp client to expect imminent delivery of a password or code. You can do this by initiating a "handshake".
A handshake is an Android intent and public class that you implement but that the WhatsApp client can start.
When a user in your app requests a password or code to be delivered to their WhatsApp number, first initiate the handshake, then call our API to send the authentication template message. When the WhatsApp client receives the message, it will perform an eligibility check, and if there are no errors, start a broadcast.
If you do not initiate the handshake before sending the message, or the message fails an eligibility check, the broadcast will not be started. Instead, the delivered message will display a one-tap autofill button, if able to do so. If unable to do so, it will display a copy code button.
The WhatsApp client performs the following checks when it receives an authentication template message. If any check fails, it will attempt to display the one-tap autofill button in the message. If unable to do so, it will fall back to a copy code button.
code_expiration_minutes
property, if present).package_name
property in the components
array upon template creation) matches the package name set on the intent. The match is determined through the getCreatorPackage
method called in the PendingIntent
object provided by your application. See One-Tap Autofill Button Class.signature_hash
property in the components array upon template creation) matches your installed app's signing key hash.Android notifications indicating receipt of a WhatsApp authentication template message will only appear on the user's Android device if:
The OTP Android SDK is available in beta. It can be used to perform handshakes, as well as other functions in both one-tap and zero-tap authentication templates.
To access SDK functionality, add the following configuration to your Gradle file:
dependencies { … implementation 'com.whatsapp.otp:whatsapp-otp-android-sdk:0.1.0' … }
To your repositories, add mavenCentral()
:
repositories { … mavenCentral() … }
Declare a Receiver and intent filter that can receive the one-time password or code. The intent filter must have the action name com.whatsapp.otp.OTP_RETRIEVED.
<receiver android:name=".app.receiver.OtpCodeReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.whatsapp.otp.OTP_RETRIEVED" /> </intent-filter> </receiver>
This is the receiver that the WhatsApp app or WhatsApp Business app will start once the authentication template message is received and it passes all eligibility checks.
Using the SDK (Preferred)
Define a class that extends BroadcastReceiver
, then define the onReceive
method, passing in your context and intent. Instantiate a WhatsAppOtpIncomingIntentHandler
object, then run the .processOtpCode()
method which will receive the intent, validate the OTP code, and handle errors.
public class OtpCodeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { WhatsAppOtpIncomingIntentHandler whatsAppOtpIncomingIntentHandler = new WhatsAppOtpIncomingIntentHandler(); whatsAppOtpIncomingIntentHandler.processOtpCode(intent, // call your function to validate (code) -> validateCode(code), // call your function to handle errors (error, exception) -> handleError(error, exception)); } }
The broadcast receiver class should look something like this:
public class OtpCodeReceiver extends BroadcastReceiver @Override public void onReceive(Context context, Intent intent) { PendingIntent pendingIntent = intent.getParcelableExtra("_ci_"); // verify source of the pendingIntent String pendingIntentCreatorPackage = pendingIntent.getCreatorPackage(); String creatorPackage = pendingIntent.getCreatorPackage(); if ("com.whatsapp".equals(creatorPackage) || "com.whatsapp.w4b".equals(creatorPackage)) { // use OTP code String otpCode = intent.getStringExtra("code"); // ... } } }
If you want the delivered message to be able to fall back to a one-tap autofill button if the message fails the eligibility check, implement this activity and intent filter in your app to receive the one-time password or code.
The intent filter must have the action name com.whatsapp.otp.OTP_RETRIEVED
.
<activity android:name=".ReceiveCodeActivity" android:enabled="true" android:exported="true" android:launchMode="standard"> <intent-filter> <action android:name="com.whatsapp.otp.OTP_RETRIEVED" /> </intent-filter> </activity>
This is the activity that the WhatsApp client will start if the message fails the eligibility check but is still eligible to display a one-tap autofill button.
If you want the message to be able to display a one-tap autofill button if the if fails an eligibility check, define the activity public class that can accept the code once the user taps the button.
public class ReceiveCodeActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); // retrieve PendingIntent from extras bundle PendingIntent pendingIntent = intent.getParcelableExtra("_ci_"); // verify source of the pendingIntent String pendingIntentCreatorPackage = pendingIntent.getCreatorPackage(); // check if creatorPackage is "com.whatsapp" -> WA consumer app Or // "com.whatsapp.w4b" -> WA business app if ("com.whatsapp".equals(creatorPackage) || "com.whatsapp.w4b".equals(creatorPackage)) { // use OTP code String otpCode = intent.getStringExtra("code"); } } }
Performing a handshake can be done by instantiating a WhatsAppOtpHandler
object and passing in your context to the .sendOtpIntentToWhatsApp()
method:
WhatsAppOtpHandler whatsAppOtpHandler = new WhatsAppOtpHandler(); whatsAppOtpHandler.sendOtpIntentToWhatsApp(context);
This example demonstrates one way to initiate a handshake with the WhatsApp app or WhatsApp Business app.
public void sendOtpIntentToWhatsApp() { // Send OTP_REQUESTED intent to both WA and WA Business App sendOtpIntentToWhatsApp("com.whatsapp"); sendOtpIntentToWhatsApp("com.whatsapp.w4b"); } private void sendOtpIntentToWhatsApp(String packageName) { /** * Starting with Build.VERSION_CODES.S, it will be required to explicitly * specify the mutability of PendingIntents on creation with either * (@link #FLAG_IMMUTABLE} or FLAG_MUTABLE */ int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? FLAG_IMMUTABLE : 0; PendingIntent pi = PendingIntent.getActivity( getApplicationContext(), 0, new Intent(), flags); // Send OTP_REQUESTED intent to WhatsApp Intent intentToWhatsApp = new Intent(); intentToWhatsApp.setPackage(packageName); intentToWhatsApp.setAction("com.whatsapp.otp.OTP_REQUESTED"); // WA will use this to verify the identity of the caller app. Bundle extras = intentToWhatsApp.getExtras(); if (extras == null) { extras = new Bundle(); } extras.putParcelable("_ci_", pi); intentToWhatsApp.putExtras(extras); getApplicationContext().sendBroadcast(intentToWhatsApp); }
You can check WhatsApp installation before offering WhatsApp as an option if you expect both WhatsApp and your app to be on the same device.
First, you need to add the following to your AndroidManifest.xml
file:
<queries> <package android:name="com.whatsapp"/> <package android:name="com.whatsapp.w4b"/> </queries>
Instantiate the WhatsAppOtpHandler
object:
WhatsAppOtpHandler whatsAppOtpHandler = new WhatsAppOtpHandler();
Check if the WhatsApp client is installed by passing the .isWhatsAppInstalled()
method as the clause in an If
statement:
If (whatsAppOtpHandler.isWhatsAppInstalled(context)) { // ... do something }
See Error Signals that can help with debugging.
Use Cloud API or On-Premises API to send authentication template messages.
Note that you must first initiate a handshake between your app and the WhatsApp client. See Handshake above.