Implement Synthetic Monitoring for SMS OTP protected pages
End-to-end monitoring of applications requiring SMS OTP 2FA
We came across an interesting use case lately: The need to implement synthetic monitoring for a page protected by OTPs delivered via SMS. Automated, as nobody wants to sit in front of a phone waiting for SMSs to arrive.
One way to automate this would be to configure a user in the target application with a Twilio mobile phone number, to which the SMS will be delivered. Then, using Twilio APIs, the Real Load test script retrieves the SMS message body and extracts the OTP from it.
Finally the script submits the OTP for validation.
Stitching it all together
In the next section we outline how you can simulate a customer journey requiring SMS OTP authentication using Real Load and Twilio.
Twilio configuration
Phone number
We’ll assume that you’re somewhat familiar with Twilio and their messaging offering. For the purpose of this PoC we’ve purchased a new Canada based phone number, as it’s cheap and doesn’t come with much regulations attached:

Disable OTP masking in Twilio
By default, twilio redacts OTPs from SMS message bodies. You’ll need to raise a ticket with Twilio support and ask them to remove the redaction for one of your phone numbers. They’ll as a few questions as to why you need this, so be ready to explain your use case.
This process is likely to take 1-2 days, so get the ball rolling on this immediately.
API keys
For Real Load to retrieve the SMS body, we’ll need to invoke Twilio APIs. This means you’ll need the following from Twilio:
- Account SID
- API Key SID
- API Key Secret
- Twilio phone number
I’d strongly recommend setting up a new restricted key which is only authorized to read SMS messages and nothing else. See following screenshot which grants read and list pemissions for messages.

Save the key and secret issued somewhere safe.
Real Load configuration
SMS OTP retriever plugin
In the Real Load portal prepare a plugin that will retrieve the OTP from the SMS messages received on the Twilio phone number.
Start the plugin wizard and configure the general settings:

On the Input/Output values tab, configure the input/output variables. If preferred, you can also hardcode these values in the pluing’s code.
| Type | Label | Variable Name |
|---|---|---|
| Input | Twilio Account SID | TWILIO_ACCOUNT_SID |
| Input | Twilio API Key | TWILIO_API_KEY |
| Input | Twilio API Secret | TWILIO_API_SECRET |
| Input | Twilio Phone # (starting with +) | TWILIO_NUMBER |
| Output | Extracted OTP | OTP |
On the source code tab, enter the Java src code to retrieve the OTP from the message body. The code of this example attempts to extract an OTP using a regexp that locates the first substring containing 6 consecutive digits. This might need to be changed to fit your use case.
import com.dkfqs.tools.javatest.AbstractJavaTest;
import com.dkfqs.tools.javatest.AbstractJavaTestPluginContext;
import com.dkfqs.tools.javatest.AbstractJavaTestPluginInterface;
import com.dkfqs.tools.javatest.AbstractJavaTestPluginSessionFailedException;
import com.dkfqs.tools.javatest.AbstractJavaTestPluginTestFailedException;
import com.dkfqs.tools.javatest.AbstractJavaTestPluginUserFailedException;
import com.dkfqs.tools.logging.LogAdapterInterface;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// add your imports here
/**
* HTTP Test Wizard Plug-In 'Twilio SMS retriever'. Plug-in Type: Normal Session
* Element Plug-In. Created by 'MikeL' at 12 Aug 2026 17:26:21 DKFQS 4.8.73
*/
@AbstractJavaTestPluginInterface.PluginResourceFiles(fileNames = {"com.dkfqs.tools.jar"})
public class TwilioSMSOTPRetriever implements AbstractJavaTestPluginInterface {
private LogAdapterInterface log = null;
private static String TWILIO_ACCOUNT_SID;
private static String TWILIO_API_KEY;
private static String TWILIO_API_SECRET;
private static String TWILIO_NUMBER;
/**
* Called by environment when the instance is created.
*
* @param log the log adapter
*/
@Override
public void setLog(LogAdapterInterface log) {
this.log = log;
}
@Override
public List<String> onInitialize(AbstractJavaTest javaTest, AbstractJavaTestPluginContext pluginContext, List<String> inputValues) throws AbstractJavaTestPluginSessionFailedException, AbstractJavaTestPluginUserFailedException, AbstractJavaTestPluginTestFailedException, Exception {
// log.message(log.LOG_INFO, "onInitialize(...)");
String local_TWILIO_ACCOUNT_SID = inputValues.get(0); // input value label 'Twilio Account SID'
String local_TWILIO_API_KEY = inputValues.get(1); // input value label 'Twilio API Key'
String local_TWILIO_API_SECRET = inputValues.get(2); // input value label 'Twilio API Secret'
String local_TWILIO_NUMBER = inputValues.get(3); // input value label 'Twilio Phone # (starting with +)'
// --- vvv --- start of specific onInitialize code --- vvv ---
this.TWILIO_ACCOUNT_SID = local_TWILIO_ACCOUNT_SID;
this.TWILIO_API_KEY = local_TWILIO_API_KEY;
this.TWILIO_API_SECRET = local_TWILIO_API_SECRET;
this.TWILIO_NUMBER = local_TWILIO_NUMBER;
// Add your onInitialize code here
// --- ^^^ --- end of specific onInitialize code --- ^^^ ---
return new ArrayList<String>(); // no output values
}
@Override
public List<String> onExecute(AbstractJavaTestPluginContext pluginContext, List<String> inputValues) throws AbstractJavaTestPluginSessionFailedException, AbstractJavaTestPluginUserFailedException, AbstractJavaTestPluginTestFailedException, Exception {
// log.message(log.LOG_INFO, "onExecute(...)");
String OTP = "000000"; // output value label 'Extracted OTP'
ArrayList<String> outputValues = new ArrayList<String>();
String encodedTo = URLEncoder.encode(TWILIO_NUMBER, StandardCharsets.UTF_8);
String url = String.format(
"https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json?To=%s&PageSize=1",
TWILIO_ACCOUNT_SID, encodedTo);
String credentials = TWILIO_API_KEY + ":" + TWILIO_API_SECRET;
String basicAuth = Base64.getEncoder()
.encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Basic " + basicAuth)
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
log.message(LogAdapterInterface.LOG_ERROR, "HTTP " + response.statusCode());
log.message(LogAdapterInterface.LOG_ERROR, response.body());
outputValues.add(OTP);
return outputValues;
}
String json = response.body();
// Extract the body field
String body = extract(json, "\"body\"\\s*:\\s*\"((?:\\\\.|[^\"\\\\])*)\"");
// Unescape common JSON escapes
if (body != null) {
body = body.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\\"", "\"")
.replace("\\\\", "\\");
}
log.message(LogAdapterInterface.LOG_INFO, "Full body : " + body);
// -------------------------------------------------------
// Extract the first continuous 6-digit sequence (no spaces)
// and store it in variable OTP
// -------------------------------------------------------
if (body != null) {
Pattern otpPattern = Pattern.compile("(?<!\\d)\\d{6}(?!\\d)");
Matcher otpMatcher = otpPattern.matcher(body);
if (otpMatcher.find()) {
OTP = otpMatcher.group(); // the 6 digits with no spaces
}
}
if (OTP.compareToIgnoreCase("000000") != 0) {
log.message(LogAdapterInterface.LOG_INFO, "Extracted OTP : " + OTP);
} else {
log.message(LogAdapterInterface.LOG_ERROR, "No 6-digit OTP found in the message body.");
}
outputValues.add(OTP);
return outputValues;
}
@Override
public List<String> onDeconstruct(AbstractJavaTestPluginContext pluginContext, List<String> inputValues) throws Exception {
// log.message(log.LOG_INFO, "onDeconstruct(...)");
// --- vvv --- start of specific onDeconstruct code --- vvv ---
// Add your onDeconstruct code here
// --- ^^^ --- end of specific onDeconstruct code --- ^^^ ---
return new ArrayList<String>(); // no output values
}
private static String extract(String json, String regex) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(json);
return m.find() ? m.group(1) : null;
}
}
Then click on the “Compile” button to compile the code.
Finally test the plugin on the Test and Save tab. To test it make sure the last SMS sent to your twilio phone number contains an OTP.

The execution details will appear in the next window. Note the full SMS message body and the extracted OTP:

Real Load test script
Record customer journey
Using the Proxy Recorder or the Desktop Companion record the customer journey that requires entering the OTP. For this example, I’ve used a local webapp that first authenticates a customer to LDAP and the performs an SMS OTP authentication.
This screenshot shows the recorded URLs using the Desktop Companion:

Update test script
Then recorded journey will then need to be edited to include execution of the plugin (highlighted in red) to retrieve the OTP and extract any other session related fields:

You’ll also need to configure variables various parameters referenced in the script, like the Twilio API credentials, phone number and the username/password used to login to the site:

You’ll then be able to use the Real Load Debugger to test the script end to end.
Configure Synthetic Monitoring
Once you’ve confirmed the script works as expected, the last step is to configure regular execution of the script. You can configure from which location(s) the script it so be executed and in which scenarios an alert is to be raised, among other things:

The execution frequency will be configured at the monitoring group level:

All done!
While the above process might look complex, writing this blog took longer than preparing the script mentioned above.
If you have a requirement to monitor an SMS OTP protected resource, we can help. You decide whether you want to take care of configuring the script by yourself or whether you want us to offer a turnkey solution. Please do not hesitate to reach out to us.