Asterisk: Sending Verification Codes by Voice

Two-factor authentication and phone number verification codes are usually sent via SMS. But what if you need to verify a landline number rather than a mobile one? A landline can’t receive SMS messages, so the only option is a voice call.

There are examples where calls are made from random numbers, and the user is required to enter the last few digits of that number. However, this method requires either the ability to spoof numbers or a large pool from which to pluck a random outgoing CID each time. Unfortunately, this isn’t available to everyone. So, in our case, we’ll simply have the «robot» read the required code out loud.

Emotional retreat

Not long ago, I received an order with exactly these specifications. But to my great regret, the client turned out to be less than forthcoming. He began changing the already agreed-upon specifications on the fly, adding redundant and illogical requirements. When I complained about this, he responded with a «I’m the boss, I know best» attitude. Even though most of the work had already been completed as a demonstration stand, I decided to show my pride and declined the job, taking everything with me. :) And now, I’ve decided to share my work with you, my dear readers! Otherwise, it would be a waste of time… And I’d truly be happy if, thanks to this material, some of you earn enough for a tiny jar of black caviar. ;)

To practice

It is assumed that before you begin reading this manual, you will already have a configured Asterisk server and an access point to the telephone network attached to it.

  1. Let’s start downloading the archive with the voiceover of the numbers and an announcement of what these numbers are… For those who are going to do the voiceover in Uzbek You’re especially lucky, because I’ve already done it for you. Download the archive here , and then place its contents where Asterisk should look for sounds. In my case, that’s the /var/lib/asterisk/sounds/custom/ directory.
  2. Almost everything we need to do will happen directly in the context. We add this context to /etc/asterisk/extensions_custom.conf with the following content:
[confirm-code]
exten => s,1,Answer()
exten => s,n,Playback(custom/uzcode)
exten => s,n,Playback(custom/uz${N1})
exten => s,n,Playback(custom/uz${N2})
exten => s,n,Playback(custom/uz${N3})
exten => s,n,Playback(custom/uz${N4})
exten => s,n,Playback(custom/confirmcode)
exten => s,n,Playback(custom/ru${N1})
exten => s,n,Playback(custom/ru${N2})
exten => s,n,Playback(custom/ru${N3})
exten => s,n,Playback(custom/ru${N4})
exten => s,n,Hangup()

I hardly need to explain anything here, but just in case, I’ll tell you that the response starts, followed by a sequential playback of voice files in two languages. In my case, the code needs to be a fixed four-digit number. Accordingly, in the context, you see four lines with variables into which the confirmation code will be broken down. You can do more or less, add copy-paste repetition , or even swap everything around to confuse everyone and make everything work…

3. We need an interface through which external software can initiate a call with a code to the user. We’ll implement this as a PHP script using the simplest calling method, using call files.

 1 && $_REQUEST['p'] <= 999999999){
		$phone = $_REQUEST['p'];
	} else {
		$result['status'] = "ERROR";
		$result['msg'] = "INCORRECT_PHONE_NUMBER";
		print(json_encode($result));
		die();
	}
	if(preg_match('/^[0-9]{4}$/',$_REQUEST['c'])){
		for($i=0; $i<4; $i++){
			$num[] = substr($_REQUEST['c'],$i,1);
		}
	} else {
		$result['status'] = "ERROR";
		$result['msg'] = "INCORRECT_CODE";
		print(json_encode($result));
		die();
	}

$callpid = substr(md5(time().rand(5, 5)),0,$callidlen);
$body="
Channel: Local/".$phone."@from-internal
CallerID: Robot <40> // Можно поменять на что-то своё.
MaxRetries: 0
RetryTime: 10
WaitTime: 10
Context: confirm-code
Extension: s
Priority: 1
AlwaysDelete Yes
Setvar: CPID=".$callpid."
Setvar: N1=".$num[0]."
Setvar: N2=".$num[1]."
Setvar: N3=".$num[2]."
Setvar: N4=".$num[3];
	if(file_put_contents("/var/spool/asterisk/outgoing/".md5(time()).".call",$body)){
		$result['status'] = "SUCCESS";
		$result['msg'] = "CALL_SENT_TO_QUEUE";
		$result['callid'] = $callpid;
		print(json_encode($result));
	} else {
		$result['status'] = "ERROR";
		$result['msg'] = "Can't create call file.";
	}
?>

Please note the check for the «B» number. My script isn’t fully functional, as it was created for testing purposes, including on internal numbers, so I strongly recommend rewriting it to suit your needs.

Also, make sure the user running the script has write access to the /var/spool/asterisk/outgoing/ directory. After Asterisk notices a call file in this directory, it will attempt to process it and then immediately delete it. The processing time and any errors are clearly visible in the Asterisk debug console.

If everything is done correctly, then calling a line like http://your.pbx.addr/call.php?p=number&c=1234 will force your server to make the desired call.