Custom MFA factor: send a second factor through any channel_
Appwrite now supports a custom MFA factor. Appwrite generates and verifies the code, and your function delivers it through WhatsApp, a voice call, or any provider you choose.

Appwrite Authentication supports email, phone (SMS), and TOTP authenticator apps as second factors. Those cover most applications, but not all of them. A fintech application wants WhatsApp, because SMS delivery is unreliable in its market. A logistics company wants a voice call to a landline. An enterprise wants its own internal push system, because the security team does not accept a third-party channel.
Until now, Appwrite Authentication did not support those channels.
The custom MFA factor changes that. Appwrite generates the code and verifies it. You decide how the code reaches the user.
What the custom factor does
The custom factor is a fifth value for the factor parameter of a multi-factor challenge, next to email, phone, totp, and recoveryCode.
When your application creates a challenge with the custom factor, Appwrite generates a six-digit code, encrypts it, and stores it. Appwrite sends no email and no SMS. A new server-only endpoint gives that code to your backend, and your backend sends it through your channel. Verification then works exactly like every other factor.
Custom token login already uses the same pattern for sign-in. The custom factor applies that pattern to the second factor.
Set up the function
The delivery step needs a server. An Appwrite Function is the simplest option, and it removes the need to store an API key.
- In the Appwrite Console, open Functions.
- Create a function with the Node.js or Rust runtime.
- Open the Settings tab of the function.
- Under Scopes, enable
users.read. - Under Execute access, add the
usersrole. Signed-in users can then call the function.
The scope matters. Appwrite passes a temporary API key to every execution in the x-appwrite-key header, and that key carries the scopes of the function. Your function reads the challenge code with that key, and you do not store a permanent secret in an environment variable.
The flow
Four calls, in this order.
- Client.
account.createMFAChallenge({ factor: 'custom' }) - Client.
functions.createExecutionwith the challenge ID - Function.
users.getMFAChallenge(userId, challengeId) - Client.
account.updateMFAChallenge({ challengeId, otp })
There is no event trigger for challenge creation. Your application calls the function. The function then reads the code. Use createExecution, and not the function domain. createExecution puts the ID of the signed-in user in the x-appwrite-user-id header. A direct call to the function domain leaves that header empty.
Create the challenge
Call this after the user completes the first factor.
The response carries the challenge ID and the expiry time. It carries no code.
Call the function
Keep the execution synchronous. Your application then knows that the delivery succeeded before it shows the code input field.
Read the code and deliver it
The function reads the code and sends it.
Complete the challenge
The user reads the code on their phone and types it.
Availability
The custom MFA factor is available in Appwrite Cloud. Update your server and client SDKs to latest.





