
Enables seamless integration of passkey-based authentication in mobile applications, supporting passkey creation and authentication via the FIDO/WebAuthn standard, with sample apps and structured error handling.
Twilio Passkeys SDK enables developers to easily add Passkeys into their existing authentication flows within their own mobile applications. The Verify Passkeys SDK supports passkeys creation and authentication using the FIDO/WebAuthn industry standard.
Verify Passkeys Overview WebAuthn
implementation("com.twilio:twilio-verify-passkeys-android:$sdkVersionName")
val twilioPasskey = TwilioPasskey(context)
https://github.com/twilio/twilio-verify-passkeys-ios
let twilioPasskey = TwilioPasskey()
implementation("com.twilio:twilio-verify-passkeys-common:$sdkVersionName")
val twilioPasskey = TwilioPasskey()
val twilioPasskey = TwilioPasskey(context)
Use the TwilioPasskey instance to create a registration by calling the create(String, AppContext) function.
The first param is a String representation of a create passkey request, check how to create passkey payload (createPayload).
The second param is an instance of a com.twilio.passkeys.AppContext, it is created by passing the current Activity instance in Android or the UIWindow instance in iOS.
You can also call the create(CreatePasskeyRequest, AppContext) function, where CreatePasskeyRequest is a wrapper object of a create passkey payload schema.
Android
val createPasskeyResult = twilioPasskey.create(createPasskeyRequest, AppContext(activity))
when(createPasskeyResult) {
is CreatePasskeyResult.Success -> {
// verify the createPasskeyResult.createPasskeyResponse against your backend and finish sign up
}
is CreatePasskeyResult.Error -> {
// handle error
}
}
iOS
let response = try await twilioPasskey.create(createPasskeyRequest: createPasskeyRequest, appContext: AppContext(uiWindow: window))
if let success = response as? CreatePasskeyResult.Success {
// verify the createPasskeyResult.createPasskeyResponse against your backend and finish sign up
} else if let error = response as? CreatePasskeyResult.Error {
// handle error
}
Use the TwilioPasskey instance to authenticate a user by calling the authenticate(String, AppContext) function.
The first param is a String representation of an authentication request, it follows the schema of an authenticate passkey payload (authenticatePayload).
The second param is an instance of a com.twilio.passkeys.AppContext, it is created by passing the current Activity instance in Android or the UIWindow instance in iOS.
You can also call the authenticate(AuthenticatePasskeyRequest, AppContext) function, which the AuthenticatePasskeyRequest is a wrapper object of an authenticate passkey payload.
Android
val authenticatePasskeyResult = twilioPasskey.authenticate(authenticatePasskeyRequest, AppContext(activity))
when(authenticatePasskeyResult) {
is AuthenticatePasskeyResult.Success -> {
// verify the authenticatePasskeyResult.authenticatePasskeyResponse against your backend
}
is AuthenticatePasskeyResult.Error -> {
// handle error
}
}
iOS
let response = try await twilioPasskey.authenticate(authenticatePasskeyRequest: authenticatePasskeyRequest, appContext: AppContext(uiWindow: window))
if let success = response as? AuthenticatePasskeyResult.Success {
// verify the authenticatePasskeyResult.authenticatePasskeyResponse against your backend and finish sign in.
} else if let error = response as? AuthenticatePasskeyResult.Error {
// handle error
}
The creation payload for creating a passkey is a String obtained by requesting your backend a challenge for registering a user, it uses the JSON schema:
{
"rp": {
"id": "your_backend",
"name": "PasskeySample"
},
"user": {
"id": "WUV...5Ng",
"name": "1234567890",
"displayName": "1234567890"
},
"challenge": "WUY...jZQ",
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
}
],
"timeout": 600000,
"excludeCredentials": [],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"residentKey": "preferred",
"userVerification": "preferred"
}
}
id
example.com).name
id
name
displayName
name, but could be more descriptive.type
"public-key" for WebAuthn and passkeys to work.alg
ES256 (Elliptic Curve Digital Signature Algorithm - ECDSA using SHA-256).-257 for RSA.authenticatorAttachment
platform → Uses a built-in authenticator (e.g., Google Credential Manager, iCloud Keychain, 1Password, etc). The credential is stored on the device and can only be used there.cross-platform → Uses a roaming authenticator (e.g., security keys like YubiKey, external FIDO2 devices). These credentials can be used across multiple devices.residentKey
required → Guarantees the credential is discoverable (stored on the authenticator). If the authenticator does not support discoverable credentials, registration will fail.preferred → Requests a discoverable credential if the authenticator supports it, but if not, a non-discoverable credential may be created instead.discouraged → Creates a non-discoverable credential, meaning the relying party (RP) must store and provide the credential ID for authentication.userVerification
required → The authenticator must verify the user (e.g., fingerprint, face recognition, PIN). If the authenticator does not support user verification, registration/authentication will fail.preferred → The authenticator will attempt user verification if supported (e.g., biometric authentication). If not, it may still proceed without verification.discouraged → User verification is not required. The credential can be used without any local authentication, making it more convenient but less secure.The authenticate payload for authenticating a user is a JSON with the schema:
{
"publicKey": {
"challenge": "WUM...2Mw",
"timeout": 300000,
"rpId": "your_backend",
"allowCredentials": [],
"userVerification": "preferred"
}
}
300000 ms = 5 minutes).example.com, this value must be "example.com".required → The authenticator must verify the user (e.g., fingerprint, face recognition, PIN). If the authenticator does not support user verification, authentication will fail.preferred → The authenticator will attempt user verification if supported (e.g., biometric authentication). If not, it may still proceed without verification.discouraged → User verification is not required. The credential can be used without any local authentication, making it more convenient but less secure.git clone https://github.com/twilio/twilio-verify-passkeys.gitBaseUrl in gradle.properties.androidApp module and run the app.In order to create passkeys you need to create the association for the sample app to communicate to the RP.
./gradlew signingreport and copying the sha256 of the keystore used to build the app.For accurate passkey testing, ensure the following on your emulator or physical device:
To enable passkeys on the backend you can follow the detailed guide. Then you should also:
./gradlew signingReport| Passkey Creation | Passkey Authentication |
|---|---|
![]() |
![]() |
git clone https://github.com/twilio/twilio-verify-passkeys.gitiosApp module in Xcode.let domain: String = "passkeys-service.com" // Example domain; replace with your backend domain<string>webcredentials:passkeys-service.com</string> <!-- Example entitlement; update with your domain -->iosApp module and run the app.In order to create passkeys you need to create the association for the sample app to communicate to the RP.
For accurate passkey testing, ensure the following on your physical device:
For simulators:
| Passkey Creation | Passkey Authentication |
|---|---|
![]() |
![]() |
shared: This module contains the shared code, including business logic, data models, and utility functions.androidApp: This module is specific to the Android platform and includes the Android sample app code.iosApp: This module is specific to the iOS platform and includes the iOS sample app code.The shared module contains code shared between Android and iOS. This includes:
The androidApp module contains Android-specific code, such as:
The iosApp module contains iOS-specific code, such as:
This section provides guidance on resolving common issues encountered when using the Android and iOS SDKs. The SDK may throw various exceptions to indicate specific errors. Below are troubleshooting steps for each exception type.
Handles errors related to WebAuthn DOM configurations for passkeys.
Android:
/.well-known/assetlinks.json file is correctly set up on your server.iOS:
/.well-known/apple-app-site-association (AASA) file is correctly configured.application/json without extension).https://app-site-association.cdn-apple.com/a/v1/<your-domain> to verify the cached version.Thrown when the user voluntarily cancels an operation.
Thrown when an operation is interrupted.
Indicates the device does not support or has disabled the passkeys feature.
Thrown when no passkey credentials are available.
Raised when the attestation object is null.
Thrown when the JSON payload is invalid.
Represents an unspecified error.
Twilio Passkeys SDK enables developers to easily add Passkeys into their existing authentication flows within their own mobile applications. The Verify Passkeys SDK supports passkeys creation and authentication using the FIDO/WebAuthn industry standard.
Verify Passkeys Overview WebAuthn
implementation("com.twilio:twilio-verify-passkeys-android:$sdkVersionName")
val twilioPasskey = TwilioPasskey(context)
https://github.com/twilio/twilio-verify-passkeys-ios
let twilioPasskey = TwilioPasskey()
implementation("com.twilio:twilio-verify-passkeys-common:$sdkVersionName")
val twilioPasskey = TwilioPasskey()
val twilioPasskey = TwilioPasskey(context)
Use the TwilioPasskey instance to create a registration by calling the create(String, AppContext) function.
The first param is a String representation of a create passkey request, check how to create passkey payload (createPayload).
The second param is an instance of a com.twilio.passkeys.AppContext, it is created by passing the current Activity instance in Android or the UIWindow instance in iOS.
You can also call the create(CreatePasskeyRequest, AppContext) function, where CreatePasskeyRequest is a wrapper object of a create passkey payload schema.
Android
val createPasskeyResult = twilioPasskey.create(createPasskeyRequest, AppContext(activity))
when(createPasskeyResult) {
is CreatePasskeyResult.Success -> {
// verify the createPasskeyResult.createPasskeyResponse against your backend and finish sign up
}
is CreatePasskeyResult.Error -> {
// handle error
}
}
iOS
let response = try await twilioPasskey.create(createPasskeyRequest: createPasskeyRequest, appContext: AppContext(uiWindow: window))
if let success = response as? CreatePasskeyResult.Success {
// verify the createPasskeyResult.createPasskeyResponse against your backend and finish sign up
} else if let error = response as? CreatePasskeyResult.Error {
// handle error
}
Use the TwilioPasskey instance to authenticate a user by calling the authenticate(String, AppContext) function.
The first param is a String representation of an authentication request, it follows the schema of an authenticate passkey payload (authenticatePayload).
The second param is an instance of a com.twilio.passkeys.AppContext, it is created by passing the current Activity instance in Android or the UIWindow instance in iOS.
You can also call the authenticate(AuthenticatePasskeyRequest, AppContext) function, which the AuthenticatePasskeyRequest is a wrapper object of an authenticate passkey payload.
Android
val authenticatePasskeyResult = twilioPasskey.authenticate(authenticatePasskeyRequest, AppContext(activity))
when(authenticatePasskeyResult) {
is AuthenticatePasskeyResult.Success -> {
// verify the authenticatePasskeyResult.authenticatePasskeyResponse against your backend
}
is AuthenticatePasskeyResult.Error -> {
// handle error
}
}
iOS
let response = try await twilioPasskey.authenticate(authenticatePasskeyRequest: authenticatePasskeyRequest, appContext: AppContext(uiWindow: window))
if let success = response as? AuthenticatePasskeyResult.Success {
// verify the authenticatePasskeyResult.authenticatePasskeyResponse against your backend and finish sign in.
} else if let error = response as? AuthenticatePasskeyResult.Error {
// handle error
}
The creation payload for creating a passkey is a String obtained by requesting your backend a challenge for registering a user, it uses the JSON schema:
{
"rp": {
"id": "your_backend",
"name": "PasskeySample"
},
"user": {
"id": "WUV...5Ng",
"name": "1234567890",
"displayName": "1234567890"
},
"challenge": "WUY...jZQ",
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
}
],
"timeout": 600000,
"excludeCredentials": [],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"residentKey": "preferred",
"userVerification": "preferred"
}
}
id
example.com).name
id
name
displayName
name, but could be more descriptive.type
"public-key" for WebAuthn and passkeys to work.alg
ES256 (Elliptic Curve Digital Signature Algorithm - ECDSA using SHA-256).-257 for RSA.authenticatorAttachment
platform → Uses a built-in authenticator (e.g., Google Credential Manager, iCloud Keychain, 1Password, etc). The credential is stored on the device and can only be used there.cross-platform → Uses a roaming authenticator (e.g., security keys like YubiKey, external FIDO2 devices). These credentials can be used across multiple devices.residentKey
required → Guarantees the credential is discoverable (stored on the authenticator). If the authenticator does not support discoverable credentials, registration will fail.preferred → Requests a discoverable credential if the authenticator supports it, but if not, a non-discoverable credential may be created instead.discouraged → Creates a non-discoverable credential, meaning the relying party (RP) must store and provide the credential ID for authentication.userVerification
required → The authenticator must verify the user (e.g., fingerprint, face recognition, PIN). If the authenticator does not support user verification, registration/authentication will fail.preferred → The authenticator will attempt user verification if supported (e.g., biometric authentication). If not, it may still proceed without verification.discouraged → User verification is not required. The credential can be used without any local authentication, making it more convenient but less secure.The authenticate payload for authenticating a user is a JSON with the schema:
{
"publicKey": {
"challenge": "WUM...2Mw",
"timeout": 300000,
"rpId": "your_backend",
"allowCredentials": [],
"userVerification": "preferred"
}
}
300000 ms = 5 minutes).example.com, this value must be "example.com".required → The authenticator must verify the user (e.g., fingerprint, face recognition, PIN). If the authenticator does not support user verification, authentication will fail.preferred → The authenticator will attempt user verification if supported (e.g., biometric authentication). If not, it may still proceed without verification.discouraged → User verification is not required. The credential can be used without any local authentication, making it more convenient but less secure.git clone https://github.com/twilio/twilio-verify-passkeys.gitBaseUrl in gradle.properties.androidApp module and run the app.In order to create passkeys you need to create the association for the sample app to communicate to the RP.
./gradlew signingreport and copying the sha256 of the keystore used to build the app.For accurate passkey testing, ensure the following on your emulator or physical device:
To enable passkeys on the backend you can follow the detailed guide. Then you should also:
./gradlew signingReport| Passkey Creation | Passkey Authentication |
|---|---|
![]() |
![]() |
git clone https://github.com/twilio/twilio-verify-passkeys.gitiosApp module in Xcode.let domain: String = "passkeys-service.com" // Example domain; replace with your backend domain<string>webcredentials:passkeys-service.com</string> <!-- Example entitlement; update with your domain -->iosApp module and run the app.In order to create passkeys you need to create the association for the sample app to communicate to the RP.
For accurate passkey testing, ensure the following on your physical device:
For simulators:
| Passkey Creation | Passkey Authentication |
|---|---|
![]() |
![]() |
shared: This module contains the shared code, including business logic, data models, and utility functions.androidApp: This module is specific to the Android platform and includes the Android sample app code.iosApp: This module is specific to the iOS platform and includes the iOS sample app code.The shared module contains code shared between Android and iOS. This includes:
The androidApp module contains Android-specific code, such as:
The iosApp module contains iOS-specific code, such as:
This section provides guidance on resolving common issues encountered when using the Android and iOS SDKs. The SDK may throw various exceptions to indicate specific errors. Below are troubleshooting steps for each exception type.
Handles errors related to WebAuthn DOM configurations for passkeys.
Android:
/.well-known/assetlinks.json file is correctly set up on your server.iOS:
/.well-known/apple-app-site-association (AASA) file is correctly configured.application/json without extension).https://app-site-association.cdn-apple.com/a/v1/<your-domain> to verify the cached version.Thrown when the user voluntarily cancels an operation.
Thrown when an operation is interrupted.
Indicates the device does not support or has disabled the passkeys feature.
Thrown when no passkey credentials are available.
Raised when the attestation object is null.
Thrown when the JSON payload is invalid.
Represents an unspecified error.