GuidesYour own stack

Twilio

Two URLs in the Twilio console. Every call is scored before your agent hears it, and on Guard a caller who looks wrong is asked to press a digit first. Your code does not change.

How it works

Twilio asks the voice webhook on a number what to do with an incoming call. You point that webhook at us. We record the call, score it, and answer with a few lines of TwiML: either hand the call straight to wherever it went before, or ask the caller to press a digit and only then hand it on. Twilio runs the TwiML on your account at your normal rates. We never touch the audio.

If we are unreachable
Twilio's "primary handler fails" URL is your old destination. If we ever do not answer, calls go straight to your agent as they did before. We are behind the number, not in the call path.

Setting it up

  1. Add the number in Murusai

    Numbers → Add a number. Pick Twilio as the carrier and set the destination: the URL the number pointed at before (your own server, or a platform's inbound URL), or a phone number or sip: address to dial. Or by API:

    RequestShell
    curl -X POST https://api.murusai.com/v1/numbers \
      -H "Authorization: Bearer $MURUSAI_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "number": "+14155550100",
        "gate_provider": "twilio",
        "gate_mode": "adaptive",
        "gate_destination": "https://agent.example.com/twilio/voice"
      }'

    The response carries a gate_token. It is the credential in the URLs below; anyone holding it can post calls against this number, so treat it like a key.

  2. Point the number at us

    Twilio console → Phone Numbers → your number → Voice configuration.

    A call comes inWebhook · POST
    https://api.murusai.com/v1/twilio/voice/gt_…
    Primary handler failsWebhook · POST
    Your destination URL, if it is a URL. Calls go straight through if we are ever down.
    Call status changesPOST
    https://api.murusai.com/v1/twilio/status/gt_…, so we learn when the call ended, and what Twilio's machine detection said.

    The dashboard shows these three, filled in, on the number's page.

  3. Choose a mode
    ModeWhat happens
    offEvery call is scored and handed straight on. You still get scores and alerts. This is what Watch does.
    adaptiveClean calls pass. A call that scores review or reject is challenged. A reject-level score latches the number into challenging everyone for thirty minutes, because one is rarely alone.
    alwaysEvery caller is challenged before they reach your agent.
  4. Make a test call

    Ring the number. On off or a clean adaptive call you reach your agent as before, and the call appears on the Activity page with a score. Set always for a moment to hear the challenge yourself.

The challenge

A single digit, one to nine, chosen fresh each time. The caller can press it or say it. Two attempts, six seconds each. A right answer that arrives faster than a person could have heard the prompt is treated as a machine guessing.

What Twilio receives · challengeXML
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Gather input="dtmf speech" numDigits="1" timeout="6" speechTimeout="auto"
          language="en-US" hints="one,two,three,four,five,six,seven,eight,nine"
          action="https://api.murusai.com/v1/twilio/gather/gt_…?attempt=1&amp;expected=4"
          method="POST" actionOnEmptyResult="true">
    <Say voice="Polly.Joanna">Thanks for calling. To continue, press or say the number 4.</Say>
  </Gather>
</Response>
What Twilio receives · handoff to a URLXML
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Redirect method="POST">https://agent.example.com/twilio/voice</Redirect>
</Response>
What Twilio receives · handoff to SIP or a phoneXML
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial answerOnBridge="true"><Sip>sip:agent@sip.example.com</Sip></Dial>
</Response>
What Twilio receives · refusedXML
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say voice="Polly.Joanna">Sorry, we couldn't verify this call. Goodbye.</Say>
  <Hangup/>
</Response>

A caller who fails both attempts is blocked on your account, the call is ended with reason gate_fail or gate_timeout, and a caller.rate_exceeded alert goes out with gate: "failed". Every challenge and its outcome (pass, fail, timeout, how the caller answered, how fast) is on the Activity page and in /v1/activity.

What Twilio tells us

The voice webhook is the one place the carrier hands over geography, caller name and the STIR/SHAKEN result. We read FromCity, FromState, FromZip, FromCountry, CallerName and StirVerstat from it and keep them on the call as attrs. The zip is what lets us notice a dozen fresh callers from one place; the attestation is a reason on its own when it fails.

On Watch and on Guard

On Watch the gate is off: point the URLs at us and every call is scored and recorded, with alerts, and handed straight on. Setting adaptive or always needs Guard, and answers 402 plan_required otherwise. The number has to be a carrier number; a number bought inside a platform never reaches the carrier's webhook, so there is nothing for us to answer.

Telnyx and SignalWire

Both speak Twilio's dialect, TeXML and LaML, with the same parameters in and the same XML out. Pick the carrier when you add the number and the URLs change to match.

CarrierWhereURLs
TelnyxCreate a TeXML Application with these URLs, then assign the number to it./v1/telnyx/voice/gt_…
/v1/telnyx/status/gt_…
SignalWirePhone Numbers → your number → Voice settings, handled by LaML webhooks./v1/signalwire/voice/gt_…
/v1/signalwire/status/gt_…

Each has a fallback URL in the same place; set it to your destination as with Twilio.

Rotating the token

The token in the URL is the credential. To rotate it, remove the number and add it again: a new token is issued and the old URLs stop working at once. The number's history stays.