Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,23 @@ import ballerinax/twilio;

public function main() returns error? {
do {
twilio:Message twilioMessage = check twilioClient->createMessage({To: "+15551234567", From: "+15550000000", Body: "Hello from WSO2 Integrator!"});
// initiate call from twilio
twilio:Call twilioCall = check twilioClient->createCall({
To: toNumber,
From: fromNumber,
Url: twimlUrl,
StatusCallback: statusCallbackUrl,
StatusCallbackMethod: "POST",
StatusCallbackEvent: ["initiated", "ringing", "answered", "completed"]
});
string? callSid = twilioCall?.sid;
log:printInfo("Call initiated", sid = callSid ?: "");

// initiate a message from twilio
twilio:Message twilioMessage = check twilioClient->createMessage({To: toNumber, From: fromNumber, Body: "Hello from WSO2 Integrator!"});
string? messageSid = twilioMessage?.sid;
log:printInfo("Message sent", sid = messageSid ?: "");

} on fail error e {
log:printError("Error occurred", 'error = e);
return e;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@

configurable string accountSid = ?;
configurable string authToken = ?;
configurable string fromNumber = ?;
configurable string toNumber = ?;
configurable string statusCallbackUrl = ?;
configurable string twimlUrl = ?;
configurable string callMessage = "Hello! This is a call from WSO2 Integrator.";
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
import ballerinax/trigger.twilio;
import ballerina/http;
import ballerina/log;

configurable int port = 8090;
configurable int twimlPort = 8091;

listener twilio:Listener twilioListener = new (port);

// Serves TwiML so Twilio knows what to say when the call is answered
// ngrok must forward to localhost:8091 for this endpoint
service /twiml on new http:Listener(twimlPort) {
resource function post voice() returns http:Response {
http:Response twimlResponse = new;
twimlResponse.setHeader("Content-Type", "application/xml");
twimlResponse.setPayload(string `<?xml version="1.0" encoding="UTF-8"?><Response><Say>${callMessage}</Say></Response>`);
log:printInfo(`TwiML ${callMessage} response sent`);
return twimlResponse;
}
}
Comment on lines +12 to +20

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the correct TwiML XML format and Content-Type header for Twilio voice call responses with the Say verb?

💡 Result:

Correct TwiML XML format (for a Voice response that uses the verb): Hello! This must be valid TwiML: the root element is and all verbs (like ) must be nested inside it.[1] Correct Content-Type header for the HTTP response containing that TwiML: Content-Type: text/xml Twilio’s docs state that when your application responds with XML, Twilio interprets it as TwiML when the response Content-Type is one of text/xml, application/xml, or text/html; text/xml is the most commonly used value.[1] For status-callback endpoints specifically, Twilio says to respond with 200 OK and Content-Type: text/xml (with an empty body, or use 204 No Content).[1] Note: If you’re returning TwiML from your webhook (not just a status callback), you still generally want XML content types; per Twilio, text/xml/application/xml/text/html are treated as the TwiML XML Instruction Set.[1]

Citations:


Use text/xml for the Content-Type header instead of application/xml.

Twilio's documentation indicates that while application/xml is accepted, text/xml is the most commonly used and recommended Content-Type for TwiML responses. The XML structure and <Say> verb formatting are correct; however, the header should be changed from "application/xml" to "text/xml" to align with Twilio best practices.

Optionally, consider adding language and voice attributes to the <Say> verb (e.g., language="en-US" and voice="man"), though these are not required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrator-default-profile/connectors/twilio_connector_sample/main.bal`
around lines 12 - 20, Update the Content-Type header in the TwiML responder:
inside the service /twiml's resource function post voice() where twimlResponse
is created and setHeader("Content-Type", "application/xml") is called, change
the header value to "text/xml"; optionally, when building the payload string for
twimlResponse.setPayload(...), add language and voice attributes to the <Say>
verb (e.g., language="en-US" voice="man") if desired.

156 changes: 82 additions & 74 deletions integrator-default-profile/connectors/twilio_trigger_sample/main.bal
Original file line number Diff line number Diff line change
@@ -1,79 +1,87 @@
import ballerina/log;
import ballerinax/trigger.twilio;
import ballerina/log;

listener twilio:Listener twilioListener = new (listenOn = listenerPort);
listener twilio:Listener twilioListener = new (listenerPort);

service twilio:SmsStatusService on twilioListener {
remote function onAccepted(twilio:SmsStatusChangeEventWrapper event) returns error|() {
do {
} on fail error err {
// handle error
return error("unhandled error", err);
}
}

remote function onQueued(twilio:SmsStatusChangeEventWrapper event) returns error|() {
do {
} on fail error err {
// handle error
return error("unhandled error", err);
}
}

remote function onSending(twilio:SmsStatusChangeEventWrapper event) returns error|() {
do {
} on fail error err {
// handle error
return error("unhandled error", err);
}
}

remote function onSent(twilio:SmsStatusChangeEventWrapper event) returns error|() {
do {
} on fail error err {
// handle error
return error("unhandled error", err);
}
}

remote function onFailed(twilio:SmsStatusChangeEventWrapper event) returns error|() {
do {
} on fail error err {
// handle error
return error("unhandled error", err);
}
}

remote function onDelivered(twilio:SmsStatusChangeEventWrapper event) returns error|() {
do {
} on fail error err {
// handle error
return error("unhandled error", err);
}
}

remote function onUndelivered(twilio:SmsStatusChangeEventWrapper event) returns error|() {
do {
} on fail error err {
// handle error
return error("unhandled error", err);
}
}

remote function onReceiving(twilio:SmsStatusChangeEventWrapper event) returns error|() {
do {
} on fail error err {
// handle error
return error("unhandled error", err);
}
}

remote function onReceived(twilio:SmsStatusChangeEventWrapper event) returns error|() {
do {
log:printInfo(event.toJsonString());
} on fail error err {
// handle error
return error("unhandled error", err);
}
// Call status service
service twilio:CallStatusService on twilioListener {

remote function onQueued(twilio:CallStatusEventWrapper event) returns error? {
log:printInfo("Call queued", callSid = event.CallSid ?: "");
}

remote function onRinging(twilio:CallStatusEventWrapper event) returns error? {
log:printInfo("Call ringing", callSid = event.CallSid ?: "");
}

remote function onInProgress(twilio:CallStatusEventWrapper event) returns error? {
log:printInfo("Call in progress", callSid = event.CallSid ?: "");
}

remote function onCompleted(twilio:CallStatusEventWrapper event) returns error? {
log:printInfo("Call completed",
callSid = event.CallSid ?: "",
duration = event.CallDuration ?: "");
}

remote function onBusy(twilio:CallStatusEventWrapper event) returns error? {
log:printInfo("Call busy", callSid = event.CallSid ?: "");
}

remote function onFailed(twilio:CallStatusEventWrapper event) returns error? {
log:printInfo("Call failed", callSid = event.CallSid ?: "");
}

remote function onNoAnswer(twilio:CallStatusEventWrapper event) returns error? {
log:printInfo("Call no answer", callSid = event.CallSid ?: "");
}

remote function onCanceled(twilio:CallStatusEventWrapper event) returns error? {
log:printInfo("Call canceled", callSid = event.CallSid ?: "");
}
}
Comment on lines +7 to 42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Twilio trigger connector documentation for event name mappings
rg -n "StatusCallbackEvent|onQueued|onInProgress|initiated|answered" --type=bal -C 3

Repository: wso2/integration-samples

Length of output: 96


🌐 Web query:

What are the valid StatusCallbackEvent values for Twilio voice calls and how do they map to Ballerina Twilio trigger service callback methods?

💡 Result:

Valid Twilio Voice StatusCallbackEvent values (Programmable Voice / REST or TwiML) are: initiated, ringing, answered, completed. Twilio’s voice webhooks docs explicitly list these events as the supported StatusCallbackEvent values when subscribing to additional status events beyond the default completed callback.[1] How they map to Ballerina Twilio trigger service callback methods (ballerinax/trigger.twilio Call status events): - initiated -> remote function onQueued(...) [2] - ringing -> remote function onRinging(...) [2] - answered -> remote function onInProgress(...) [2] - completed -> remote function onCompleted(...) [2] Note: The Ballerina trigger uses CallStatusEventWrapper callback method names (onQueued/onRinging/onInProgress/onCompleted) rather than the exact Twilio StatusCallbackEvent strings; the mapping above reflects which wrapper callback names the trigger exposes for those same call lifecycle phases.[2][1]

Citations:


Remove unused Twilio status callbacks or clarify their purpose.

The service defines callbacks for onBusy, onFailed, onNoAnswer, and onCanceled, but Twilio voice webhooks only support four StatusCallbackEvent values: initiated, ringing, answered, and completed. These four additional callbacks will never be invoked. Either remove them or document if they are intended for a different Twilio service type (e.g., messaging). The currently configured callbacks (onQueued, onRinging, onInProgress, onCompleted) correctly map to their respective Twilio events.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrator-default-profile/connectors/twilio_trigger_sample/main.bal` around
lines 7 - 42, The extra Twilio status callbacks onBusy, onFailed, onNoAnswer,
and onCanceled are unused for voice StatusCallbackEvent and should be removed or
clearly documented if they target a different Twilio service; update the service
by deleting these remote functions (onBusy, onFailed, onNoAnswer, onCanceled) if
you only handle voice events, or add a comment explaining their intended purpose
and the alternative Twilio event types if they’re required for messaging/other
webhooks, keeping only onQueued, onRinging, onInProgress, and onCompleted for
voice.


// SMS status service
service twilio:SmsStatusService on twilioListener {

remote function onAccepted(twilio:SmsStatusChangeEventWrapper event) returns error? {
log:printInfo("SMS accepted", messageSid = event.MessageSid ?: "");
}

remote function onQueued(twilio:SmsStatusChangeEventWrapper event) returns error? {
log:printInfo("SMS queued", messageSid = event.MessageSid ?: "");
}

remote function onSending(twilio:SmsStatusChangeEventWrapper event) returns error? {
log:printInfo("SMS sending", messageSid = event.MessageSid ?: "");
}

remote function onSent(twilio:SmsStatusChangeEventWrapper event) returns error? {
log:printInfo("SMS sent", messageSid = event.MessageSid ?: "");
}

remote function onFailed(twilio:SmsStatusChangeEventWrapper event) returns error? {
log:printInfo("SMS failed", messageSid = event.MessageSid ?: "");
}

remote function onDelivered(twilio:SmsStatusChangeEventWrapper event) returns error? {
log:printInfo("SMS delivered", messageSid = event.MessageSid ?: "");
}

remote function onUndelivered(twilio:SmsStatusChangeEventWrapper event) returns error? {
log:printInfo("SMS undelivered", messageSid = event.MessageSid ?: "");
}

remote function onReceiving(twilio:SmsStatusChangeEventWrapper event) returns error? {
log:printInfo("SMS receiving", messageSid = event.MessageSid ?: "");
}

remote function onReceived(twilio:SmsStatusChangeEventWrapper event) returns error? {
string fromNumber = event.From ?: "";
string msgBody = event.Body ?: "";
log:printInfo("SMS received",
messageSid = event.MessageSid ?: "",
'from = fromNumber,
body = msgBody);
}
}