I finally figured this out. I wasn’t setting the headers properly.
Below is the final code. I put inline comments to explain what each line is doing in case someone else wants to do something similar in the future.
[sms-invite]
; First, we have to answer the call
exten => s,1,Answer
; Then we get the Caller ID from the system
exten => s,n,set(caller=${CALLERID(number)})
; Our Database API needs the Caller ID in xxx-xxx-xxxx format, so let's do that
exten => s,n,set(cid=$[${caller:0:3}~~"-"~~${caller:3:3}~~"-"~~${caller:6:4}])
; Get the client ID from API
exten => s,n,set(clientid=${URIENCODE(${CURL(https://0.0.0.0/api.php?cid=${cid})})})
; Get the case manager's phone number from API
exten => s,n,set(from=${URIENCODE(${CURL(https://0.0.0.0/api.php?cid=${cid})})})
; Set the destination as the caller ID
exten => s,n,set(to=${CALLERID(number)})
; Build the body of the message
exten => s,n,set(body=$["Blah Blah Blah: www.google.com/q?" ~~ ${clientid}])
; Set the authentication variables for Twilio
exten => s,n,set(twilio_sid=<<< Twilio SID >>>)
exten => s,n,set(twilio_token=<<< Twilio Token >>>)
; Set the headers and send the request via CURL
exten => s,n,set(CURLOPT(userpwd)=${twilio_sid}:${twilio_token})
exten => s,n,Set(response=${CURL(https://api.twilio.com/2010-04-01/Accounts/${twilio_sid}/Messages.json,To=1${to}&From=1${from}&Body=${body})})
; Analyze the result using REGEX and go to success if successful
exten => s,n,GotoIf($[${REGEX("\"status\": \"queued\"" ${response})} = 1]?success)
; If it wasn't successful, this code will be executed, call will be return to Custom Destination for further routing
exten => s,n,Playback(tt-monkeysintro)
exten => s,n,Return()
; If successful, this code will be executed and the call will be terminated
exten => s,n(success),Playback(tt-weasels)
exten => s,n,Goto(app-blackhole,hangup,1)