Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

REST API: Mutation and Conflict Resolution


When dealing with real-time updates, some Twilio API resources utilize the If-Match(link takes you to an external page) and ETag(link takes you to an external page) HTTP headers for conditional mutation. Every response containing these objects will return an ETag value, which you can compare against an If-Match header.

The following resources currently support this form of conflict resolution:

  • Task Resource
  • Sync List Item Resource

Let's walk through a workflow using the Task resource:


Create and Update a Resource

create-and-update-a-resource page anchor

We often make requests with the idea of "optimistic concurrency". For example, let's say that you request to create a Task, like so:

Create a Task

create-a-task page anchor
Node.js
Python
C#
Java
Go
PHP
Ruby
twilio-cli
curl

_18
// Download the helper library from https://www.twilio.com/docs/node/install
_18
const twilio = require("twilio"); // Or, for ESM: import twilio from "twilio";
_18
_18
// Find your Account SID and Auth Token at twilio.com/console
_18
// and set the environment variables. See http://twil.io/secure
_18
const accountSid = process.env.TWILIO_ACCOUNT_SID;
_18
const authToken = process.env.TWILIO_AUTH_TOKEN;
_18
const client = twilio(accountSid, authToken);
_18
_18
async function createTask() {
_18
const task = await client.taskrouter.v1
_18
.workspaces("WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
_18
.tasks.create({ attributes: JSON.stringify({ foo: "bar" }) });
_18
_18
console.log(task.accountSid);
_18
}
_18
_18
createTask();

Output

_31
{
_31
"account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"age": 25200,
_31
"assignment_status": "pending",
_31
"attributes": "{\"foo\": \"bar\"}",
_31
"date_created": "2014-05-14T18:50:02Z",
_31
"date_updated": "2014-05-15T07:26:06Z",
_31
"task_queue_entered_date": null,
_31
"virtual_start_time": "2014-05-14T18:50:02Z",
_31
"priority": 1,
_31
"reason": "Test Reason",
_31
"sid": "WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_queue_sid": "WQaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_channel_sid": "TCaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_channel_unique_name": "unique",
_31
"timeout": 60,
_31
"url": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workspace_sid": "WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow_sid": "WWaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow_friendly_name": "Example Workflow",
_31
"task_queue_friendly_name": "Example Task Queue",
_31
"ignore_capacity": false,
_31
"routing_target": "WKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"addons": "{}",
_31
"links": {
_31
"task_queue": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/TaskQueues/WQaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Workflows/WWaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workspace": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"reservations": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Reservations"
_31
}
_31
}

This will create a Task with the attributes {"foo": "bar"}. Now you can update that object through the REST API.

POST A: Update Task attributes to bar-SFO

post-a-update-task-attributes-to-bar-sfo page anchor
Node.js
Python
C#
Java
Go
PHP
Ruby
twilio-cli
curl

_19
// Download the helper library from https://www.twilio.com/docs/node/install
_19
const twilio = require("twilio"); // Or, for ESM: import twilio from "twilio";
_19
_19
// Find your Account SID and Auth Token at twilio.com/console
_19
// and set the environment variables. See http://twil.io/secure
_19
const accountSid = process.env.TWILIO_ACCOUNT_SID;
_19
const authToken = process.env.TWILIO_AUTH_TOKEN;
_19
const client = twilio(accountSid, authToken);
_19
_19
async function updateTask() {
_19
const task = await client.taskrouter.v1
_19
.workspaces("WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
_19
.tasks("WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
_19
.update({ attributes: JSON.stringify({ foo: "bar-SFO" }) });
_19
_19
console.log(task.accountSid);
_19
}
_19
_19
updateTask();

Output

_31
{
_31
"account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"age": 25200,
_31
"assignment_status": "pending",
_31
"attributes": "{\"foo\": \"bar-SFO\"}",
_31
"date_created": "2014-05-14T18:50:02Z",
_31
"date_updated": "2014-05-15T07:26:06Z",
_31
"task_queue_entered_date": "2014-05-14T18:50:02Z",
_31
"virtual_start_time": "2023-08-02T12:34:56Z",
_31
"priority": 0,
_31
"reason": "Test Reason",
_31
"sid": "WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_queue_sid": "WQaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_channel_sid": "TCaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_channel_unique_name": "task-channel",
_31
"timeout": 60,
_31
"url": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow_sid": "WWaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workspace_sid": "WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow_friendly_name": "Test Workflow",
_31
"task_queue_friendly_name": "Test Queue",
_31
"addons": "{}",
_31
"ignore_capacity": false,
_31
"routing_target": "WKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"links": {
_31
"task_queue": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/TaskQueues/WQaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Workflows/WWaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workspace": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"reservations": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Reservations"
_31
}
_31
}

You'll get a response back, and the attributes will now be {"foo": "bar-SFO"}. This is optimistic concurrency - you assume that you are the only person making changes to the Task, so foo used to be bar, and now it's bar-SFO.

This might work a lot of the time, but let's say there is an argument over whether the attribute should be bar-SFO or bar-DEN. Now you have two POST requests trying to update the Task in quick succession: POST A (the same request as above) and POST B.

What will happen in this condition?

Post B: Update Attributes to bar-DEN

post-b-update-attributes-to-bar-den page anchor
Node.js
Python
C#
Java
Go
PHP
Ruby
twilio-cli
curl

_19
// Download the helper library from https://www.twilio.com/docs/node/install
_19
const twilio = require("twilio"); // Or, for ESM: import twilio from "twilio";
_19
_19
// Find your Account SID and Auth Token at twilio.com/console
_19
// and set the environment variables. See http://twil.io/secure
_19
const accountSid = process.env.TWILIO_ACCOUNT_SID;
_19
const authToken = process.env.TWILIO_AUTH_TOKEN;
_19
const client = twilio(accountSid, authToken);
_19
_19
async function updateTask() {
_19
const task = await client.taskrouter.v1
_19
.workspaces("WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
_19
.tasks("WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
_19
.update({ attributes: JSON.stringify({ foo: "bar-DEN" }) });
_19
_19
console.log(task.accountSid);
_19
}
_19
_19
updateTask();

Output

_31
{
_31
"account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"age": 25200,
_31
"assignment_status": "pending",
_31
"attributes": "{\"foo\": \"bar-DEN\"}",
_31
"date_created": "2014-05-14T18:50:02Z",
_31
"date_updated": "2014-05-15T07:26:06Z",
_31
"task_queue_entered_date": "2014-05-14T18:50:02Z",
_31
"virtual_start_time": "2023-08-02T12:34:56Z",
_31
"priority": 0,
_31
"reason": "Test Reason",
_31
"sid": "WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_queue_sid": "WQaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_channel_sid": "TCaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_channel_unique_name": "task-channel",
_31
"timeout": 60,
_31
"url": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow_sid": "WWaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workspace_sid": "WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow_friendly_name": "Test Workflow",
_31
"task_queue_friendly_name": "Test Queue",
_31
"addons": "{}",
_31
"ignore_capacity": false,
_31
"routing_target": "WKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"links": {
_31
"task_queue": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/TaskQueues/WQaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Workflows/WWaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workspace": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"reservations": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Reservations"
_31
}
_31
}


Resolve Multiple Updates to a Resource

resolve-multiple-updates-to-a-resource page anchor

The last writer wins. Because we're operating on the model of optimistic concurrency, POST B will simply overwrite POST A.

Fortunately, you can avoid these race conditions. You'll notice that the attributes aren't the only thing that changed when you updated your resource: Your ETag header will have incremented from 0 to 1 in the response.

Now, you can use the If-Match header to check that the version of the Resource is up-to-date before sending your update through. First, GET the Task to retrieve the ETag header. Then pass this revision as a value in the If-Match header when you post your update. This ensures that, if you are not referencing the most up-to-date revision, the update will be rejected.

Update with If-Match Header

update-with-if-match-header page anchor
Node.js
Python
C#
Java
Go
PHP
Ruby
twilio-cli
curl

_19
// Download the helper library from https://www.twilio.com/docs/node/install
_19
const twilio = require("twilio"); // Or, for ESM: import twilio from "twilio";
_19
_19
// Find your Account SID and Auth Token at twilio.com/console
_19
// and set the environment variables. See http://twil.io/secure
_19
const accountSid = process.env.TWILIO_ACCOUNT_SID;
_19
const authToken = process.env.TWILIO_AUTH_TOKEN;
_19
const client = twilio(accountSid, authToken);
_19
_19
async function updateTask() {
_19
const task = await client.taskrouter.v1
_19
.workspaces("WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
_19
.tasks("WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
_19
.update({ attributes: JSON.stringify({ foo: "bar-DEN" }) });
_19
_19
console.log(task.accountSid);
_19
}
_19
_19
updateTask();

Output

_31
{
_31
"account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"age": 25200,
_31
"assignment_status": "pending",
_31
"attributes": "{\"foo\": \"bar-DEN\"}",
_31
"date_created": "2014-05-14T18:50:02Z",
_31
"date_updated": "2014-05-15T07:26:06Z",
_31
"task_queue_entered_date": "2014-05-14T18:50:02Z",
_31
"virtual_start_time": "2023-08-02T12:34:56Z",
_31
"priority": 0,
_31
"reason": "Test Reason",
_31
"sid": "WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_queue_sid": "WQaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_channel_sid": "TCaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"task_channel_unique_name": "task-channel",
_31
"timeout": 60,
_31
"url": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow_sid": "WWaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workspace_sid": "WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow_friendly_name": "Test Workflow",
_31
"task_queue_friendly_name": "Test Queue",
_31
"addons": "{}",
_31
"ignore_capacity": false,
_31
"routing_target": "WKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"links": {
_31
"task_queue": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/TaskQueues/WQaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workflow": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Workflows/WWaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"workspace": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
_31
"reservations": "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Reservations"
_31
}
_31
}

Note the addition of the If-Match header. Now, this operation will fail with a 412 Response because the submitted ETag value is 0 and the current Attributes are different.

When you're testing or building a simple application, this might not be a critical step. If, however, your resource can be mutated by multiple sources at a time, using these headers will help ensure that you don't accidentally overwrite and/or lose any updates.



Rate this page: