I'm trying to do something similar (Programmable Chat, Twilio Flex) and here is what got me to a working state:
- Instead of creating a task manually I opted-in for a default Studio flow that you get when initializing Flex. I started with this doc but it only gives a theoretical understanding. I figured out the actual webhook by using Twilio Flex Sample Web App and tracing channels it creates.
- Whenever a web/mobile client wants to chat I call my custom endpoint to perform the needed setup.
// After creating a Twilio Programmable Chat Channel set up a Studio webhook
app.post("/create-task", function(request, response) {
// see full code below
chatService.channels
.create({
// See full code below
})
.then(channel => {
const webhookUrl = channel.links.webhooks;
const options = {
url: webhookUrl,
method: "POST",
auth: {
user: accountSid,
pass: authToken
},
form: {
Type: "studio",
"Configuration.FlowSid": twilioFlowSid
}
};
return new Promise((resolve, reject) => {
requestLibrary.post(options, function(error, response, body) {
if (!error) {
resolve(channel);
} else {
reject(error);
}
});
});
});
// see full code below
});
- That only gets you halfway. By now you should see a task popping up in Flex but if Agent accepts it he can't message back nor does he see messages from the client. I assume there is some magic involved when you do it from Twilio Widget.
I was able to figure out that for channels I create manually when I accept a task as an Agent the Agent doesn't join a Channel. So the number of participants is 1 instead of 2.
This means that Agent basically can't see Channel data and send messages to it. Maybe my Channel is missing some metadata in attributes but I wasn't able to figure it out yet.
What I did is I used a callback that you get whenever the task status changes and in particulary, I used reservation.accepted
event to add the Agent as a member of a channel manually. You can add callback under TaskRouter Settings at the bottom.
app.post("/accept-task-callback", function(request, response) {
const { TaskAttributes, WorkerSid, WorkerName, EventType } = request.body;
const { channelSid } = JSON.parse(TaskAttributes);
console.log("received event", EventType);
if (EventType !== "reservation.accepted") {
response.send("OK");
return;
}
console.log("Adding member", WorkerSid, WorkerName, "on event", EventType);
chatService
.channels(channelSid)
.members.create({ identity: WorkerName })
.then(member => {
response.send({
instruction: "accept"
});
})
.catch(error => {
console.error(error);
response.send({
instruction: "reject"
});
});
});
Here is a full code
// This is Express App
app.post("/create-task", function(request, response) {
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const workspaceSid = process.env.TWILIO_WORKSPACE_SID;
const workflowSid = process.env.TWILIO_WORKFLOW_SID;
const twilioFlowSid = "FW..."
// Identity of the Twilio Programmable Chat user who initiates a dialog. You get it from you signin or whatever
const username = request.body || "Nancy Drew Support";
chatService.channels
.create({
type: "private",
friendlyName: username,
attributes: JSON.stringify({
status: "ACTIVE",
from: username,
channel_type: "web"
})
})
.then(channel => {
const webhookUrl = channel.links.webhooks;
const options = {
url: webhookUrl,
method: "POST",
auth: {
user: accountSid,
pass: authToken
},
form: {
Type: "studio",
"Configuration.FlowSid": twilioFlowSid
}
};
return new Promise((resolve, reject) => {
requestLibrary.post(options, function(error, response, body) {
if (!error) {
resolve(channel);
} else
reject(error);
}
});
});
})
.then(async channel => {
// Join as a Customer requesting Support
return chatService
.channels(channel.sid)
.members.create({ identity: username });
})
.then(member => {
// return back channel sid we created
response.send({ channelSid: member.channelSid });
})
.catch(error => {
console.log(error);
response.fail(error);
});
});
app.post("/accept-task-callback", function(request, response) {
const { TaskAttributes, WorkerSid, WorkerName, EventType } = request.body;
const { channelSid } = JSON.parse(TaskAttributes);
console.log("received event", EventType);
if (EventType !== "reservation.accepted") {
response.send("OK");
return;
}
console.log("Adding member", WorkerSid, WorkerName, "on event", EventType);
chatService
.channels(channelSid)
.members.create({ identity: WorkerName })
.then(member => {
response.send({
instruction: "accept"
});
})
.catch(error => {
console.error(error);
response.send({
instruction: "reject"
});
});
});
I still have a lot to figure out. Unfortunately, Flex documentation isn't great at this point and misses some very basic tutorials like "Configuring Twilio Programmable Chat with Flex from ground up". This kind of tutorials would help everyone to understand how all Twilio APIs come together in such a powerful tool like Flex.
Anyway, hope my answer helps. I can try to elaborate further if you have questions.