What callers get
-
A code prompt
The line asks for a six-digit code and moves on as soon as the sixth digit is pressed.
-
Hashed codes
The document holds the SHA-256 hash of each code, so reading the document doesn’t reveal a code directly.
-
A line per agent
Each code belongs to one agent, who is greeted by name and put through to their own number.
-
One try per call
A wrong code ends the call, so every guess costs the caller a new call.
How the pieces fit
| Piece | Where it lives | What it does |
|---|---|---|
| Main document | voiceml.org, /s/ URL | The number’s voice URL. Asks for the code. |
| Verification document | voiceml.org, /r/ URL | The action of the <Gather>. Hashes the code, finds the agent, and connects them. |
| Agents’ phones | Any phone numbers | The line each agent is put through to. |
Before you start
- A VoiceML account with a phone number. See VoiceML or start a free trial.
- A six-digit code for each person, and the number each one should be put through to.
- The SHA-256 hash of each code. On Linux,
printf '%s' 427351 | sha256sumprints it; on macOS, useshasum -a 256in place ofsha256sum. Useprintfrather thanecho, which adds a newline and changes the hash.
The codes and numbers in these documents are samples. The hashes are for the codes 427351 and 916204, and the numbers use the fictional 555-0100 to 555-0199 range. Replace them all before you publish.
Step 1: Publish the verification document
Replace each hash with the hash of a real code, and each number with that person’s line. Then publish it at voiceml.org. It contains Liquid tags, so it is published to an /r/ URL. Keep the URL exactly as returned, key included, and don’t post it publicly.
verify.xml
<Response>
{% comment %} The document stores SHA-256 hashes of the codes, never the codes themselves. {% endcomment %}
{% assign entered = Digits | sha256 %}
{% case entered %}
{% when "110165df04d9ea4a1b72a2425756bebf16c0d2c44fe30547d0e6932e854d6231" %}
{% assign agent = "Phelps" %}
{% assign line = "+12025550101" %}
{% when "7e76c9ad0714b0cfe0dc354163290b5aaf3defa8e5820ee5730ace1725fc0a1a" %}
{% assign agent = "Briggs" %}
{% assign line = "+12025550102" %}
{% endcase %}
{% if agent %}
<Say>Good morning, Mr. {{ agent }}. Your mission, should you choose to accept it, is waiting on the secure line.</Say>
<Dial timeout="20" ringTone="us">
<Number>{{ line }}</Number>
</Dial>
<Say>This call will self-destruct in five seconds. Good luck, Mr. {{ agent }}.</Say>
<Hangup/>
{% else %}
<!-- One attempt per call: guessing costs the caller a new call each time -->
<Say>Access denied. Goodbye.</Say>
<Hangup/>
{% endif %}
</Response>
Step 2: Publish the main document and point the number at it
Replace VERIFY_URL with the /r/ URL from step 1 and publish. Set the /s/ URL it returns as the phone number’s voice URL.
main.xml
<Response>
<Gather input="dtmf" numDigits="6" action="VERIFY_URL" method="POST" timeout="10">
<Pause length="1"/>
<Say>Secure line. Enter your six-digit agent code.</Say>
</Gather>
<Say>No code was entered. Goodbye.</Say>
<Hangup/>
</Response>
Step 3: Test it
- Call the number and enter one of your codes. You are greeted by name and the matching phone rings.
- Call again and enter any other six digits. You hear “Access denied” and the call ends.
How the check works
The <Gather numDigits="6"> sends the code to the verification document as Digits. There, {% assign entered = Digits | sha256 %} turns it into the same 64-character hash that sha256sum prints, and a {% case entered %} compares it with each agent’s hash. A match sets the agent’s name and number; <Number>{{ line }}</Number> dials whichever number was set. Anything else falls through to “Access denied” and <Hangup/>.
Sending wrong codes to a hang-up instead of a second try is deliberate. A document can’t count attempts or remember earlier calls, so it can’t lock anyone out; ending the call is the only brake it has on guessing.
Where the show turns up
| Where | Reference |
|---|---|
| The agents | Jim Phelps and Dan Briggs, who led the team. |
| The greeting | “Good morning, Mr. Phelps,” and “Your mission, should you choose to accept it.” |
| The sign-off | The taped message that “will self-destruct in five seconds.” |
Limits of this design
- Hashing hides the codes, but doesn’t make them secret. A six-digit code has a million possibilities, and anyone holding the document can hash all of them in seconds and find the match. Keep the document’s URL private, and use longer codes where it matters.
- No lockout. Each wrong guess ends the call, but nothing stops someone from calling again. Watch your call logs for repeated failures.
- The caller’s number isn’t checked. To require both a code and a known number, add the number lists from the call screening example.
- Changing a code means publishing again. Published documents can’t be edited, so a new code is a new document and a new URL for the number.
Credits
- The Impossible Missions Force, its taped briefings, and their sign-off come from Mission: Impossible (CBS, 1966–1973), created by Bruce Geller. Steven Hill plays Dan Briggs, who leads the team in the first season, and Peter Graves plays Jim Phelps, who leads it after him.
- The line, its agents’ codes, and its phone numbers are fictional in this example. VoiceTel is not affiliated with the show or its rights holders.
- References: the
<Gather>reference and the voiceml.org publishing and integration guide, which lists the Liquid filters a document can use,sha256among them.

