← Back to blog

Tutorial

WhatsApp OTP for Flutter Apps: Secure Mobile Verification

Flutter apps should never ship provider API keys inside the APK, IPA, or web bundle. A secure WhatsApp OTP integration uses Flutter as the user interface, your backend as the security boundary, and DNZ WhatsApp OTP as the delivery system.

WhatsApp OTP FlutterFlutter OTP authenticationWhatsApp verification Flutter

The problem in mobile OTP

Mobile developers often want the app to call the OTP provider directly because it looks simpler. That approach leaks secrets, makes abuse limits hard to enforce, and gives attackers a stable API target from a decompiled app. Flutter builds are easy to inspect enough that any secret embedded in the client should be considered public.

The correct model is a backend-mediated flow. Flutter sends a phone number to your own API. Your API validates the request, generates the code, stores the verification state, calls DNZ `/api/send-otp`, and returns only a neutral success response to the app.

Recommended screens

Build two screens or two states in one screen: request code and verify code. The request state captures the phone number, shows country-code guidance, and disables the send button while the backend is processing. The verify state captures the code, shows a resend timer, and does not reveal whether the number exists unless the user is already authenticated.

Keep error messages practical. Network errors should invite retry. Rate limits should show a wait message. Invalid code should not reset the whole screen. The user should be able to correct the code without retyping the phone number.

Future<void> requestOtp(String phone) async {
  final response = await http.post(
    Uri.parse('https://api.example.com/auth/request-otp'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'phone': phone}),
  );

  if (response.statusCode != 200) {
    throw Exception('Unable to request OTP');
  }
}

Future<bool> verifyOtp(String phone, String code) async {
  final response = await http.post(
    Uri.parse('https://api.example.com/auth/verify-otp'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'phone': phone, 'code': code}),
  );

  return response.statusCode == 200;
}

Backend contract for Flutter

The Flutter app should call endpoints such as `/auth/request-otp` and `/auth/verify-otp`. Those endpoints are yours, not DNZ engine endpoints. Your backend then calls DNZ using `apiKey`, `number`, and `message`. This keeps the DNZ key private and lets you add device checks, IP throttling, and account rules.

If you support multiple languages, pass a locale to your backend and generate the OTP message in the right language. DNZ sends the text you provide, so Arabic and English templates can both work as long as your backend chooses the template intentionally.

Testing checklist

Test the happy path, invalid code, expired code, resend cooldown, invalid phone number, offline device, backend error, and DNZ rate limit response. Also test app backgrounding while the user waits for the code. The verification state should survive a short navigation or app pause if your UX requires it.

Before launch, confirm that the app bundle contains no DNZ key, no engine URL intended to be private, and no hard-coded test numbers. Keep public URLs public and secrets on the server.

FAQ

Can Flutter call DNZ directly?

It should not. Flutter should call your backend, and your backend should call DNZ.

Does DNZ support Arabic OTP messages?

Yes. The API sends the message text supplied by your backend, including Arabic text.

Where should I verify the OTP?

Verify it on your backend, not inside the Flutter app.

Related content

Connect Flutter to WhatsApp OTP

Use DNZ WhatsApp OTP with your backend and build a clean mobile verification flow.

Open OTP dashboard