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
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.
- The user signs in with their email and password. The response tells you that MFA is required.
- Your MFA screen lets the user choose how to receive the code, for example Text message or WhatsApp.
- Your app asks Frontegg for a code and passes
channel: 'whatsapp'. - Frontegg generates the code and calls your
SEND_SMSprehook withchannel: 'whatsapp'and the code. - Your prehook sends the code through your WhatsApp provider and tells Frontegg not to send an SMS.
- 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.
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.
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.
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.
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.
Frontegg has no WhatsApp-specific error. How a failure is handled depends on what your prehook returns:
| Situation | Result |
|---|---|
The prehook returns useFronteggSms: false | Frontegg sends no SMS. The code reaches the user only through your WhatsApp message. |
The prehook returns useFronteggSms: true | Frontegg sends the code over SMS. |
| The prehook fails, times out, or returns no usable response | Frontegg sends the code over SMS, whatever channel the user asked for. |
No SEND_SMS prehook is configured | Frontegg sends the code over SMS. |
The prehook returns a block verdict | The 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 fails | The request fails. |
Avoid duplicate messages
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.