Custom Challenge

🆔 Custom Challenge – Overview

A Custom Challenge allows the REL-ID server to initiate a custom-defined, app-specific step during activation or credential flows. This is useful for cases such as:

  • User consent workflows
  • External KYC integrations
  • Location/GPS validation
  • Biometric vendor-specific checks

This challenge is delivered via the onHandleCustomChallenge event and must be rendered and handled by the host application.


🔐 Security & Use Cases

  • Fully handled on the client side, enabling deep integration with third-party flows
  • REL-ID server acts as a coordinator, expecting the client to fulfill a known contract
  • Often used in regulated environments (e.g., Aadhaar consent, custom biometrics)

🔄 How It Works

  1. SDK triggers onHandleCustomChallenge
  2. App receives a structured payload with UI instructions
  3. App displays input field and description
  4. User submits their data eg: National ID
  5. App sends the input using setCustomChallengeResponse()

onHandleCustomChallenge Event

Register an event listener for the event, use event name as onHandleCustomChallenge and payload as described below

📥 Sample Payload

{
  "userID": "testuser",
  "challenge": "{ 
    \"chlng_name\": \"NationalID\", 
    \"chlng_idx\": 4,
    \"chlng_info\": [
      { \"key\": \"Response Label\", \"value\": \"NationalID\" },
      { \"key\": \"description\", \"value\": \"Enter your National ID\" }
    ],
    \"attempts_left\": 3,
    \"max_attempts_count\": 0,
    \"chlng_resp\": [
      { \"challenge\": \"NationalID\", \"response\": \"\" }
    ],
    \"chlng_type\": 1,
    \"chlng_response_validation\": false
  }",
  "status": {
    "statusCode": 100,
    "statusMessage": "Success"
  },
  "error": {
    "longErrorCode": 0,
    "shortErrorCode": 0,
    "errorString": "Success"
  }
}

🧾 Key Fields Explained

FieldTypeDescription
userIDStringUser identifier for whom the challenge is issued
challengeString (JSON)Embedded JSON string containing all challenge metadata
chlng_nameStringName of the custom challenge (e.g., "NationalID")
chlng_idxIntegerIndex or order of the challenge
chlng_infoArrayAdditional info (e.g., description, labels) to be shown in the UI
attempts_leftIntegerNumber of remaining attempts
max_attempts_countIntegerMaximum allowed attempts (0 means unlimited or unspecified)
chlng_respArrayPlaceholder for submitting the user's response
chlng_typeIntegerType of challenge (used for routing logic in SDK or backend)
chlng_promptArrayUI prompts or labels if applicable
chlng_response_validationBooleanIndicates if SDK should auto-validate the response
challenge_response_policyArrayOptional policy definitions for validating the response
chlng_cipher_specArrayCipher spec, if response encryption is needed
sub_chlng_countIntegerNumber of sub-challenges inside this challenge
chlngs_per_batchIntegerUsed when challenges are batched together
status, errorObjectsStatus codes and messages for handling response state

✅ Submitting the Response

📤 setCustomChallengeResponse API

The setCustomChallengeResponse method is used to submit a response to a custom challenge delivered via onHandleCustomChallenge, such as a National ID prompt.


🔧 Basic Flow

  1. Parse the challenge field (JSON string) from the event.
  2. Populate the response field inside chlng_resp.
  3. Convert the updated object back to a JSON string.
  4. Submit it via setCustomChallengeResponse().

📱 Platform-specific Code Samples

Follow below steps to add the user entered value in the challenge JSON:

  1. Parse the challenge JSON and retrieve the “chlng_resp” JsonArray.
  2. From that JsonArray, retrieve the first JsonObject and set the value of the “response” key withthe value the user has entered.
💙 React Native
// Register event listener
let onHandleCustomChallengeSubscription = rdnaEventRegistery.addListener(
  'onHandleCustomChallenge',
  this.onHandleCustomChallenge.bind(this)
);

// Handler method
onHandleCustomChallenge(event) {
  let parsedChallenge = JSON.parse(event.challenge);
  parsedChallenge.chlng_resp[0].response = "123456789"; // User input

  RdnaClient.setCustomChallengeResponse(
    JSON.stringify(parsedChallenge),
    (response) => {
      console.log("Custom challenge submitted successfully:", response);
    }
  );
}

🟣 Flutter
// Register event listener
rdnaClient.on(
  RdnaClient.onHandleCustomChallenge,
  onHandleCustomChallenge
);

// Handler method
void onHandleCustomChallenge(RDNAHandleCustomChallenge event) {
  Map<String, dynamic> challenge = jsonDecode(event.challenge);
  challenge["chlng_resp"][0]["response"] = "123456789";

  rdnaClient.setCustomChallengeResponse(jsonEncode(challenge));
}

🧩 Cordova
// Register event listener
document.addEventListener(
  'onHandleCustomChallenge',
  this.onHandleCustomChallenge.bind(this),
  false
);

// Handler method
onHandleCustomChallenge(event) {
  let parsedChallenge = JSON.parse(event.challenge);
  parsedChallenge.chlng_resp[0].response = "123456789";

  com.uniken.rdnaplugin.RdnaClient.setCustomChallengeResponse(
    () => console.log("Success"),
    (err) => console.error("Error", err),
    [JSON.stringify(parsedChallenge)]
  );
}

🍏 iOS (Objective-C)
// Callback method
(void)onHandleCustomChallenge:(NSString *)userID
                    challenge:(NSString*)challengeJson
                      status:(RDNARequestStatus *)status
                       error:(RDNAError *)error {

  NSError *error;
  NSData *data = [challengeJson dataUsingEncoding:NSUTF8StringEncoding];
  NSMutableDictionary *challengeDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];

  challengeDict[@"chlng_resp"][0][@"response"] = @"123456789";

  NSData *updatedData = [NSJSONSerialization dataWithJSONObject:challengeDict options:0 error:nil];
  NSString *updatedString = [[NSString alloc] initWithData:updatedData encoding:NSUTF8StringEncoding];

  [rdnaInstance setCustomChallengeResponse:updatedString];
}

🤖 Android (Java)
// Callback method
void onHandleCustomChallenge(String userID,
                             String challengeJSON,
                             RDNARequestStatus requestStatus,
                             RDNAError error) {

  JSONObject challenge = new JSONObject(challengeJSON);
  JSONArray chlngResp = challenge.getJSONArray("chlng_resp");
  chlngResp.getJSONObject(0).put("response", "123456789");

  rdna.setCustomChallengeResponse(challenge.toString());
}

🧠 Tips

  • Make sure to sanitize and validate user input before inserting it.
  • Always use the original challenge object and only modify the chlng_resp[].response field.
  • Handle the response or errors appropriately in the callback.

❌ Error Handling

Status CodeMeaningApp Action
153Attempts exhaustedTerminate challenge, follow fallback
166User is lockedShow a pop-up dialog with the error message and prevent further login attempts until the cooling period ends

🧠 Notes

  • This challenge is dynamic — your app must parse and render UI based on chlng_info.
  • Validate and sanitize user input (e.g., numeric-only, length).
  • Use attempts_left to control retry logic.