Skip to content

MFA Transaction Signing

Enrol a user, set a PIN, and sign a transaction from C#, on Android and iOS alike.

Read MFA Transaction Signing first for the three-call flow and what the token proves.

Every call returns JSON as a string, which you parse with the matching result type. They are synchronous and they do network I/O, so call them from a coroutine or a worker rather than from a frame you care about.

using Byteria.SDK;
// 1. Once per (user, device), after login. No biometric prompt appears here.
MfaResult enrolment = MfaResult.Parse(AlphynSDK.EnrollForMfa(userId));
if (!enrolment.success) {
Debug.LogWarning($"enrol failed: {enrolment.errorCode}");
}
// 2. Set or change the PIN. Verified and hashed ON THE SERVER.
char[] newPin = pinInput.text.ToCharArray();
MfaResult pinResult = MfaResult.Parse(AlphynSDK.SetMfaPin(userId, newPin));
// 3. Per transaction, after the user clears your confirmation screen.
char[] pin = pinInput.text.ToCharArray();
string context = "{\"amount\":\"250.00\",\"currency\":\"TRY\",\"toIBAN\":\"TR12…\"}";
ActionVerificationResult result =
ActionVerificationResult.Parse(AlphynSDK.SignAction(userId, "TRANSFER", context, pin));
if (result.success) {
StartCoroutine(SendToYourBackend(result.verificationToken));
}

SetMfaPin and SignAction zero every char[] they are given before returning, on every path. Build the array from your input field and pass it; do not keep a string copy, because a C# string is immutable and cannot be wiped.

public static string EnrollForMfa(string userId)
public static string SetMfaPin(string userId, char[] newPin, char[] oldPin = null)
public static string SignAction(string userId, string actionLabel, string contextJson,
char[] pin = null, AlphynSubject subject = null)
ParameterTypeNotes
userIdstringyour identifier for the logged-in user, not one Alphyn issues
newPin / oldPinchar[]zeroed by the call. Omit oldPin on a first set
actionLabelstringyour own name for the operation, e.g. TRANSFER
contextJsonstringthe transaction as a JSON object. Its hash is bound into the signature
pinchar[]optional. Omitting it omits the factor rather than failing
subjectAlphynSubjectoptional. null is byte-identical to declaring nothing

success, errorCode, errorMessage, securityLevel, enrollmentId. Parse with MfaResult.Parse(json).

success, verificationToken, expiresAtMs, errorCode, errorMessage. Parse with ActionVerificationResult.Parse(json).

Read the token from verificationToken. It is the whole JWT and it is what your backend verifies; it is not a display value.

var subject = new AlphynSubject.Builder()
.Tc("10000000146")
.Phone("+905342223344")
.Build();
ActionVerificationResult result = ActionVerificationResult.Parse(
AlphynSDK.SignAction(userId, "TRANSFER", context, pin, subject));

Build() never throws and never fails, for any input. AlphynSubject carries the three values; it does not validate or hash them. That work belongs to the native core, so that the accepted forms have one implementation per platform rather than a third one in C# that could disagree with the other two.

A field that does not conform is dropped, never corrected, and a subject in which nothing survived produces a request byte-identical to one that declared none. See Subject identity for the accepted forms and for why a token must be treated as identifying data.

If the running Alphyn core is older than subject support, the identity is dropped, the transaction proceeds, and the SDK logs a single warning. Declaring an identity can never cost a transaction.

  • Signing an in-game purchase, a withdrawal or an account change so your backend can prove afterwards that a specific user’s device authorised those exact values
  • Adding a second factor to a high-risk action without an SMS round trip
  • Correlating a signed transaction with a person across institutions, through a subject token
IEnumerator ConfirmTransfer(string userId, string iban, string amount) {
char[] pin = pinInput.text.ToCharArray();
string context = $"{{\"amount\":\"{amount}\",\"currency\":\"TRY\",\"toIBAN\":\"{iban}\"}}";
var subject = new AlphynSubject.Builder().Tc(customerTckn).Build();
ActionVerificationResult result = ActionVerificationResult.Parse(
AlphynSDK.SignAction(userId, "TRANSFER", context, pin, subject));
if (!result.success) {
// THREAT_DETECTED is a refusal, not a transport failure. Do not retry silently.
ShowError(result.errorCode);
yield break;
}
// Your backend validates this token before it moves anything.
yield return YourApi.Transfer(iban, amount, result.verificationToken);
}

The token is evidence, not a decision. Your backend must validate it server-side and compare the echoed context against the transfer it is about to perform; see Backend verification.