Skip to content
Last updated

Deliver MFA codes over WhatsApp

You can let users receive their MFA code over WhatsApp as an alternative to SMS, by combining your own login UI with the SEND_SMS prehook.

Frontegg generates the code and passes it to your SEND_SMS prehook, together with the channel the user selected. Your application lets the user choose WhatsApp, requests the code, and sends it through your WhatsApp provider.


Prerequisites

  • Frontegg's embedded login.
  • An account with a WhatsApp messaging provider, such as the WhatsApp Business Platform or Twilio.
  • Phone number (SMS) MFA enabled for your environment, with users enrolled with a phone number.

How it works

  1. The user signs in with their email and password. The response tells you that MFA is required.
  2. Your MFA screen lets the user choose how to receive the code, for example Text message or WhatsApp.
  3. Your app asks Frontegg for a code and passes channel: 'whatsapp'.
  4. Frontegg generates the code and calls your SEND_SMS prehook with channel: 'whatsapp' and the code.
  5. Your prehook sends the code through your WhatsApp provider and tells Frontegg not to send an SMS.
  6. The user enters the code, and your app verifies it with Frontegg.

The user's phone number is the same one used for SMS MFA. No separate enrollment is needed for WhatsApp.


Step 1: Deliver the code from the SEND_SMS prehook

Create a prehook for the Send SMS (SEND_SMS) event, as described in the prehooks guide. The event payload carries a channel field set to 'sms' or 'whatsapp', next to the user's phone number, the code, and the SMS template type (0 for MFA):

{
  "eventKey": "SEND_SMS",
  "data": {
    "channel": "whatsapp",
    "type": 0,
    "user": {
      "id": "8f2c1a7e-3d4b-4c9a-9e61-0b7d2f5a1c3e",
      "email": "jane@example.com",
      "phoneNumber": "+15551234567"
    },
    "body": "Your login code is: 123456. Do not share it with anyone.",
    "data": {
      "code": "123456"
    }
  }
}

When channel is 'whatsapp', send the code yourself and return useFronteggSms: false, so Frontegg does not send an SMS as well. For any other channel, return useFronteggSms: true and Frontegg sends the SMS as usual:

async function onEvent(eventData) {
  const { channel, user, data } = eventData.data;

  if (channel !== 'whatsapp') {
    return { verdict: 'allow', response: { useFronteggSms: true } };
  }

  await sendWhatsAppMessage(user.phoneNumber, data.code);
  return { verdict: 'allow', response: { useFronteggSms: false } };
}

exports.onEvent = onEvent;

sendWhatsAppMessage stands for the call to your WhatsApp provider. channel is always present and defaults to 'sms', so a SEND_SMS prehook that ignores it keeps working exactly as before.


Step 2: Sign the user in

Authenticate the user with their email and password. When MFA is required, the response includes an mfaToken and the user's enrolled phone devices:

curl -X POST https://{your-domain}/identity/resources/auth/v1/user \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@example.com", "password": "user-password"}'

{
  "mfaRequired": true,
  "mfaToken": "eyJhbGciOi...",
  "mfaDevices": {
    "phones": [{ "id": "c4b7e2d9-5a16-4f3e-8b20-7d9e1f6a3b58", "phoneNumber": "+1555****567" }]
  }
}

Keep the mfaToken and the phone device id for the next steps.


Step 3: Request the code over WhatsApp

When the user chooses WhatsApp, request a code for the phone device and pass channel: 'whatsapp'. Omit channel, or pass 'sms', to deliver the code over SMS:

curl -X POST https://{your-domain}/identity/resources/auth/v1/user/mfa/sms/{deviceId} \
  -H "Content-Type: application/json" \
  -d '{"mfaToken": "eyJhbGciOi...", "channel": "whatsapp"}'

The response returns an otcToken. Each request generates a new code and a new otcToken, so when the user asks to resend, or switches between SMS and WhatsApp, call this endpoint again and keep the latest otcToken. The endpoint is limited to 10 requests per hour per IP address.


Step 4: Verify the code

Send the code the user entered, along with the mfaToken and the latest otcToken:

curl -X POST https://{your-domain}/identity/resources/auth/v1/user/mfa/sms/{deviceId}/verify \
  -H "Content-Type: application/json" \
  -d '{"mfaToken": "eyJhbGciOi...", "otcToken": "a1b2c3...", "code": "123456"}'

A successful verification returns the user's tokens, the same as any other MFA login. The code length and expiration follow your existing SMS MFA settings.


When delivery fails

Frontegg has no WhatsApp-specific error. How a failure is handled depends on what your prehook returns:

SituationResult
The prehook returns useFronteggSms: falseFrontegg sends no SMS. The code reaches the user only through your WhatsApp message.
The prehook returns useFronteggSms: trueFrontegg sends the code over SMS.
The prehook fails, times out, or returns no usable responseFrontegg sends the code over SMS, whatever channel the user asked for.
No SEND_SMS prehook is configuredFrontegg sends the code over SMS.
The prehook returns a block verdictThe request fails with 410 and error ER-01037, for example Failed to send sms to +1555****567. The prehook's own error message is not returned.
The prehook is set to fail close and the call failsThe request fails.

Avoid duplicate messages

If your prehook sends the WhatsApp message but then fails or times out, Frontegg also sends an SMS, and the user receives two codes. Return useFronteggSms: false as soon as the WhatsApp message is sent, and keep your provider call well within the 5-second prehook timeout.