Skip to main content
Every webhook delivery uses the common envelope with the same id, apiVersion, createdAt, type, and data fields. The sections below document only the per-event data wrapper - the part that varies by event type. The type field on the envelope determines which schema below applies.

Quick index

Event typeWhen it fires
message.receivedAn inbound SMS, MMS, or message was received by a Quo number.
message.deliveredAn outbound message was delivered. Not a read receipt.
message.failedAn outbound message failed to deliver. Includes a carrier or provider error code when available.
call.ringingA call started ringing. Fires for incoming and outgoing. Context is narrower than other call events.
call.answeredA call was connected. answeredByUserId identifies the Quo-side user associated with the answer when known. Outgoing calls fire this event for voicemail pickup too.
call.completedA call ended. Terminal lifecycle event with the final status and duration.
call.forwardedAn incoming call was forwarded. Includes the forwarding phone numbers.
call.missedAn incoming call ended without being answered. Outgoing calls do not fire this event.
call.recording.completedA call recording finished processing. May arrive after call.completed.
call.summary.completedA call summary finished generating. May arrive long after the call ends.
call.transcript.completedA call transcript finished processing. Order vs. summary is not guaranteed.
call.voicemail.completedA voicemail was left and finished processing. Use resource.callId to correlate with the source call lifecycle.
contact.updatedA contact was created or its fields changed.
contact.deletedA contact was deleted. Resource shape matches contact.updated.
task.createdA task was created.
task.updatedA task’s title or description changed.
task.deletedA task was deleted.
task.completedA task was marked complete.
task.reopenedA completed task was reopened.
task.assignedA task was assigned to a user.
task.unassignedA task was unassigned.
task.overdueA task has passed its due date.
task.linkedA task was linked to a conversation, activity, or phone number.
task.unlinkedA task was unlinked from a conversation.
task.duedate.updatedA task’s due date was changed.
task.duedate.removedA task’s due date was cleared.
To see any event payload against your endpoint without waiting for live traffic, call POST /webhooks/:id/events/test with the eventType you want to inspect.

Field semantics

Two patterns appear in multiple events. Both indicate why a field may be empty rather than implying “no data exists”.

contacts.lookupStatus

Explains why contacts.ids may be empty:
StateMeaning
matchedQuo found one or more matching contacts. ids is populated.
noneQuo checked for matching contacts and found none. ids is [].
unavailableQuo could not determine contact matches because the event lacked sufficient context. Treat ids as unknown, not empty.

participants.resolution

Explains whether participant context was resolved on call events:
StateMeaning
availableWorkspace and external participants are populated correctly.
unavailableParticipant context could not be resolved. Treat empty arrays as unknown rather than as no participants.

Common type aliases

Several events reuse these types. Each event references them by name rather than redefining them inline.
type MessageStatus =
  | 'queued' | 'sending' | 'sent' | 'delivered' | 'undelivered' | 'failed'
  | 'receiving' | 'received' | 'accepted' | 'scheduled' | 'read'
  | 'partially_delivered' | 'canceled'

interface MessageContext {
  phoneNumberId: string | null
  conversationId: string | null
  userId: string
  contacts: { ids: string[]; lookupStatus: 'matched' | 'none' | 'unavailable' }
  senderIdentifier: string
  recipientIdentifiers: string[]
}

interface CallContext {
  phoneNumberId: string | null
  conversationId: string | null
  phoneNumberType: 'shared' | 'private' | 'external' | null
  userId: string
  contacts: { ids: string[]; lookupStatus: 'matched' | 'none' | 'unavailable' }
  participants: {
    workspace: string[]
    external: string[]
    resolution: 'available' | 'unavailable'
  }
}

interface CallRingingContext {
  phoneNumberId: string | null
  conversationId: string | null
  userId: string
  participants: {
    workspace: string[]
    external: string[]
    resolution: 'available' | 'unavailable'
  }
}

interface ContactContext {
  userId: string
  sharedWithIds: string[]
}

interface TaskContext {
  orgId: string
  actorId: string
  phoneNumberId: string | null
  conversationId: string | null
  activityId: string | null
  phoneNumberGroupId: string | null
}

message.received

An inbound message was received by Quo. Use this as your inbound trigger.
interface MessageReceivedEvent {
  type: 'message.received'
  data: {
    resource: {
      id: string
      direction: 'incoming'
      text: string
      media: Array<{ type?: string | null; url: string }>
      status: MessageStatus
      createdAt: string
    }
    context: MessageContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "AC-message",
      "direction": "incoming",
      "text": "hello",
      "media": [],
      "status": "received",
      "createdAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "senderIdentifier": "+15550001111",
      "recipientIdentifiers": ["+15550002222"]
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
senderIdentifier and recipientIdentifiers are raw participant identifiers. They are usually E.164 phone numbers, but direct-number and internal flows can emit non-phone identifiers.

message.delivered

An outbound message was delivered. This is delivery confirmation, not a read receipt.
interface MessageDeliveredEvent {
  type: 'message.delivered'
  data: {
    resource: {
      id: string
      direction: 'outgoing'
      text: string
      media: Array<{ type?: string | null; url: string }>
      status: MessageStatus
      createdAt: string
    }
    context: MessageContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "AC-message",
      "direction": "outgoing",
      "text": "hello",
      "media": [],
      "status": "delivered",
      "createdAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "senderIdentifier": "+15550002222",
      "recipientIdentifiers": ["+15550001111"]
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
If you correlate deliveries with conversations, key on context.conversationId and context.phoneNumberId rather than links.quo.

message.failed

An outbound message failed to deliver. resource.errorCode contains the carrier or provider failure code when one is available; it is null when the failure reason is unknown.
interface MessageFailedEvent {
  type: 'message.failed'
  data: {
    resource: {
      id: string
      direction: 'outgoing'
      text: string
      status: MessageStatus
      errorCode: string | null
      createdAt: string
    }
    context: MessageContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "AC-message",
      "direction": "outgoing",
      "text": "hello",
      "status": "failed",
      "errorCode": "30006",
      "createdAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "senderIdentifier": "+15550002222",
      "recipientIdentifiers": ["+15550001111"]
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
errorCode is the raw code returned by the carrier or messaging provider. It is present only on message.failed and is absent from all other message events.

call.ringing

call.ringing fires once at the start of an incoming or outgoing call. Its context is narrower than later call events — it carries participants (the workspace and external phone numbers) so you can identify the caller at ring time, but it omits contacts and phoneNumberType, which are not resolved yet. For an incoming call the caller is in participants.external; the Quo number that was dialed is in participants.workspace.
interface CallRingingEvent {
  type: 'call.ringing'
  data: {
    resource: {
      id: string
      direction: 'incoming' | 'outgoing'
      createdAt: string
      updatedAt: string
    }
    context: CallRingingContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "direction": "incoming",
      "createdAt": "2026-04-13T12:00:00.000Z",
      "updatedAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "userId": "US123",
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
Use participants.external to look up the related contact via the API at ring time. When participant context cannot be resolved, participants.resolution is unavailable and the arrays may be empty — treat empty arrays as unknown rather than as no participants. Later call events include richer context such as contacts and phoneNumberType.

call.answered

A call was connected. answeredByUserId is the Quo user associated with the answer when known. It is not the external party who answered an outgoing call.
interface CallAnsweredEvent {
  type: 'call.answered'
  data: {
    resource: {
      id: string
      direction: 'incoming' | 'outgoing'
      createdAt: string
      answeredAt: string | null
      answeredByUserId: string | null
      updatedAt: string | null
    }
    context: CallContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "direction": "incoming",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "answeredAt": "2026-04-13T12:00:00.000Z",
      "answeredByUserId": "US123",
      "updatedAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
call.answered also fires on outgoing calls when the recipient’s voicemail picks up. Treat this event as “the call connected” rather than “a person is on the line.”

call.completed

A call ended. This is the terminal lifecycle event with the final status, duration, and timestamps. Recording, summary, transcript, and voicemail artifacts (if any) arrive as separate events and may be delayed by minutes.
interface CallCompletedEvent {
  type: 'call.completed'
  data: {
    resource: {
      id: string
      direction: 'incoming' | 'outgoing'
      status: CallCompletedStatus
      createdAt: string
      answeredAt: string | null
      completedAt: string | null
      updatedAt: string | null
      duration: number | null
      hasVoicemail: boolean
    }
    context: CallContext
    links: { quo: string | null }
  }
}

type CallCompletedStatus =
  | 'answered' | 'unanswered' | 'failed' | 'forwarded'
  | 'abandoned' | 'ai-handled' | 'unknown'
{
  "data": {
    "resource": {
      "id": "AC-call",
      "direction": "incoming",
      "status": "answered",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "answeredAt": "2026-04-13T12:00:00.000Z",
      "completedAt": "2026-04-13T12:00:55.000Z",
      "updatedAt": "2026-04-13T12:00:55.000Z",
      "duration": 55,
      "hasVoicemail": false
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
Possible values for status:
ValueMeaning
answeredThe call connected. For outgoing calls, this may be either a person answering or the recipient’s voicemail picking up.
unansweredThe call ended without being answered. May still have a voicemail — check hasVoicemail.
failedThe call could not be placed or connected.
forwardedThe call was forwarded to another number.
abandonedThe caller hung up before the call was answered.
ai-handledAn AI agent handled the call.
unknownQuo could not classify the final state.
  • hasVoicemail indicates whether a voicemail was left. The voicemail itself arrives as a separate call.voicemail.completed event.
  • duration is in seconds and may be null for calls that never connected.

call.forwarded

An incoming call was forwarded. This event carries the phone numbers involved in the forward. Other call lifecycle events do not include forwardedFrom or forwardedTo.
interface CallForwardedEvent {
  type: 'call.forwarded'
  data: {
    resource: {
      id: string
      createdAt: string
      updatedAt: string | null
      forwardedFrom: string
      forwardedTo: string
    }
    context: CallContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "updatedAt": "2026-04-13T12:00:00.000Z",
      "forwardedFrom": "+15550000001",
      "forwardedTo": "+15550000003"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}

call.missed

An incoming call ended without being answered. Outgoing calls do not produce this event — they terminate with call.completed and a non-answered status. The payload is intentionally minimal. To get the full call shape, look up the call by resource.id in the API.
interface CallMissedEvent {
  type: 'call.missed'
  data: {
    resource: {
      id: string
      createdAt: string
      updatedAt: string
    }
    context: CallContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "updatedAt": "2026-04-13T12:00:30.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}

call.recording.completed

A call recording finished processing. May arrive after call.completed. recordings is always an array — an empty [] means no recording metadata is available in this payload.
interface CallRecordingCompletedEvent {
  type: 'call.recording.completed'
  data: {
    resource: {
      id: string
      direction: 'incoming' | 'outgoing'
      createdAt: string
      answeredAt: string | null
      completedAt: string | null
      updatedAt: string | null
      duration: number | null
      recordings: CallRecording[]
    }
    context: CallContext
    links: { quo: string | null }
  }
}

interface CallRecording {
  id: string | null
  duration: number | null
  startTime: string | null
  type: string | null
  url: string | null
}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "direction": "incoming",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "answeredAt": "2026-04-13T12:00:00.000Z",
      "completedAt": "2026-04-13T12:00:55.000Z",
      "updatedAt": "2026-04-13T12:01:05.000Z",
      "duration": 55,
      "recordings": [
        {
          "id": "REabc123",
          "duration": 55,
          "startTime": "2026-04-13T12:00:00.000Z",
          "type": "audio/mpeg",
          "url": "https://recordings.example.com/REabc123.mp3"
        }
      ]
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
Download or persist the recording file rather than relying on the URL for long-term access.

call.summary.completed

A call summary finished processing. This is a summary readiness event, not a call-ended event — the call may have ended much earlier. Trust processingStatus, not arrival time, when interpreting summary state.
interface CallSummaryCompletedEvent {
  type: 'call.summary.completed'
  data: {
    resource: {
      callId: string
      processingStatus: 'absent' | 'in-progress' | 'completed' | 'failed'
      summary: string[] | null
      nextSteps: string[] | null
      fromPhoneNumber: string | null
      handledByAiAgent: boolean
      answeredByUserId: string | null
      jobs: AgentCallSummaryJob[]
    }
    context: CallContext
    links: { quo: string | null }
  }
}

interface AgentCallSummaryJob {
  icon: string
  name: string
  result: { data: Array<{ name: string; value: string | number | boolean }> }
}
{
  "data": {
    "resource": {
      "callId": "AC-summary",
      "processingStatus": "completed",
      "summary": ["Customer asked for pricing details."],
      "nextSteps": ["Send follow-up email."],
      "fromPhoneNumber": null,
      "handledByAiAgent": false,
      "answeredByUserId": null,
      "jobs": []
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
Notes:
  • summary and nextSteps are arrays when processingStatus === 'completed'; otherwise null.
  • jobs is an empty array when no AI agent job metadata is available.
  • Use callId to correlate the summary with its source call and any transcript event.

call.transcript.completed

A call transcript finished processing. Transcript and summary events are independent and may arrive in either order for the same call.
interface CallTranscriptCompletedEvent {
  type: 'call.transcript.completed'
  data: {
    resource: {
      callId: string
      createdAt: string
      duration: number
      processingStatus: 'absent' | 'in-progress' | 'completed' | 'failed'
      dialogue: DialogueEntry[] | null
    }
    context: CallContext
    links: { quo: string | null }
  }
}

interface DialogueEntry {
  userId: string | null
  identifier: string | null
  content: string
  start: number
  end: number
}
{
  "data": {
    "resource": {
      "callId": "AC-transcript",
      "createdAt": "2026-04-13T12:00:01.000Z",
      "duration": 42,
      "processingStatus": "completed",
      "dialogue": [
        { "userId": "US123", "identifier": null, "content": "Thanks for calling, how can I help?", "start": 0, "end": 3 },
        { "userId": null, "identifier": "+15550000002", "content": "Hi, I wanted to ask about pricing.", "start": 3, "end": 7 }
      ]
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
Not every dialogue line maps to a Quo user. External participants surface as identifier without a userId; internal speakers surface as userId with or without an identifier.

call.voicemail.completed

A voicemail was left and finished processing. resource.id is the voicemail activity id. resource.callId is the source call activity id, or null if the source call could not be resolved. Use resource.callId to correlate the voicemail with the rest of the call lifecycle.
interface CallVoicemailCompletedEvent {
  type: 'call.voicemail.completed'
  data: {
    resource: {
      id: string
      voicemailId: string | null
      callId: string | null
      direction: 'incoming' | 'outgoing'
      duration: number
      from: string
      to: string
      transcript: string | null
      recordingUrl: string | null
      createdAt: string
      updatedAt: string
    }
    context: CallContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "AC-voicemail",
      "voicemailId": "VM123",
      "callId": "AC-source-call",
      "direction": "incoming",
      "duration": 18,
      "from": "+15550000002",
      "to": "+15550000001",
      "transcript": "Hi, leaving a quick message about the proposal...",
      "recordingUrl": "https://recordings.example.com/VM123.mp3",
      "createdAt": "2026-04-13T12:01:00.000Z",
      "updatedAt": "2026-04-13T12:01:10.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
  • transcript is null while processing or if transcription was not available for the voicemail.
  • Download or persist the voicemail recording rather than relying on recordingUrl for long-term access.
  • If the source call context cannot be fully resolved, participants.resolution may be unavailable and participant arrays may be empty. If the source call itself cannot be resolved, resource.callId will be null and phoneNumberType will be null.

contact.updated

A contact was created or its fields changed. Use updatedAt for ordering and freshness checks. Contact events are workspace-wide; see Subscription rules.
interface ContactUpdatedEvent {
  type: 'contact.updated'
  data: {
    resource: ContactResource
    context: ContactContext
    links: { quo: string | null }
  }
}

interface ContactResource {
  id: string
  firstName: string | null
  lastName: string | null
  company: string | null
  role: string | null
  location: string | null
  source: string | null
  externalId: string | null
  emails: Array<{ value: string; type: 'email' }>
  phoneNumbers: Array<{ value: string; type: 'phone-number' }>
  customFields: CustomField[]
  createdAt: string
  updatedAt: string
}

type CustomField =
  | { name: string; key: string; id?: string; type: 'string' | 'url' | 'address'; value: string | null }
  | { name: string; key: string; id?: string; type: 'number'; value: number | null }
  | { name: string; key: string; id?: string; type: 'boolean'; value: boolean }
  | { name: string; key: string; id?: string; type: 'date'; value: string | null }
  | { name: string; key: string; id?: string; type: 'multi-select'; value: string[] }
{
  "data": {
    "resource": {
      "id": "CT123",
      "firstName": "Jane",
      "lastName": "Doe",
      "company": null,
      "role": null,
      "location": null,
      "source": null,
      "externalId": null,
      "emails": [{ "value": "jane@example.com", "type": "email" }],
      "phoneNumbers": [{ "value": "+15551234567", "type": "phone-number" }],
      "customFields": [
        { "name": "Department", "key": "department", "id": "i1", "type": "multi-select", "value": ["sales"] }
      ],
      "createdAt": "2026-01-01T00:00:00.000Z",
      "updatedAt": "2026-04-13T12:00:00.000Z"
    },
    "context": { "userId": "US123", "sharedWithIds": ["US456"] },
    "links": { "quo": "https://my.quo.com/contacts/CT123" }
  }
}
Notes:
  • customFields[].id is omitted when the source item has no id.
  • Invalid number or date custom field values normalize to null.
  • multi-select values are always arrays.

contact.deleted

A contact was deleted. The resource shape matches contact.updated; the event discriminator is type. Soft-deleted email addresses, phone numbers, and custom fields are removed from the payload rather than delivered with a deletion marker.
interface ContactDeletedEvent {
  type: 'contact.deleted'
  data: {
    resource: ContactResource
    context: ContactContext
    links: { quo: string | null }
  }
}
The JSON shape is identical to contact.updated, with type: "contact.deleted" on the envelope.

task.created

A task was created. resource.dueDate is null when no due date was set at creation time.
interface TaskCreatedEvent {
  type: 'task.created'
  data: {
    resource: {
      id: string
      title: string
      description: string
      dueDate: string | null
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123",
      "title": "Follow up with customer",
      "description": "Discuss pricing and onboarding next steps.",
      "dueDate": "2026-04-20T18:00:00.000Z"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.updated

A task’s title or description changed. updatedFields reports which fields were part of this update. updatedValue is present only when isUpdated is true; when isUpdated is false the field was unchanged and updatedValue is absent.
interface TaskUpdatedEvent {
  type: 'task.updated'
  data: {
    resource: {
      id: string
      updatedFields: {
        title: { isUpdated: boolean; updatedValue?: string }
        description: { isUpdated: boolean; updatedValue?: string }
      }
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123",
      "updatedFields": {
        "title": { "isUpdated": true, "updatedValue": "Follow up with customer ASAP" },
        "description": { "isUpdated": false }
      }
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.deleted

A task was deleted. Only the task ID is included in the resource.
interface TaskDeletedEvent {
  type: 'task.deleted'
  data: {
    resource: {
      id: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.completed

A task was marked complete. Only the task ID is included in the resource.
interface TaskCompletedEvent {
  type: 'task.completed'
  data: {
    resource: {
      id: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.reopened

A completed task was reopened. Only the task ID is included in the resource.
interface TaskReopenedEvent {
  type: 'task.reopened'
  data: {
    resource: {
      id: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.assigned

A task was assigned to a user. context.actorId is the user who performed the assignment; resource.assignedTo is the user the task was assigned to.
interface TaskAssignedEvent {
  type: 'task.assigned'
  data: {
    resource: {
      id: string
      assignedTo: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123",
      "assignedTo": "US456"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.unassigned

A task was unassigned. context.actorId is the user who removed the assignment; resource.unassignedFrom is the user who was previously assigned.
interface TaskUnassignedEvent {
  type: 'task.unassigned'
  data: {
    resource: {
      id: string
      unassignedFrom: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123",
      "unassignedFrom": "US456"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.overdue

A task has passed its due date without being completed. resource.dueDate is the missed due date in ISO 8601 format.
interface TaskOverdueEvent {
  type: 'task.overdue'
  data: {
    resource: {
      id: string
      dueDate: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123",
      "dueDate": "2026-04-20T18:00:00.000Z"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}
context.actorId reflects the task owner at the time the overdue check ran.

task.linked

A task was linked to a conversation, activity, or phone number. The linked resource is reflected in context.conversationId, context.activityId, and context.phoneNumberId.
interface TaskLinkedEvent {
  type: 'task.linked'
  data: {
    resource: {
      id: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.unlinked

A task was unlinked from a conversation. context.conversationId is the conversation the task was detached from.
interface TaskUnlinkedEvent {
  type: 'task.unlinked'
  data: {
    resource: {
      id: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.duedate.updated

A task’s due date was set or changed. resource.updatedDueDate is the new due date in ISO 8601 format.
interface TaskDueDateUpdatedEvent {
  type: 'task.duedate.updated'
  data: {
    resource: {
      id: string
      updatedDueDate: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123",
      "updatedDueDate": "2026-04-27T18:00:00.000Z"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

task.duedate.removed

A task’s due date was cleared. Only the task ID is included in the resource.
interface TaskDueDateRemovedEvent {
  type: 'task.duedate.removed'
  data: {
    resource: {
      id: string
    }
    context: TaskContext
    links: { quo: string | null }
  }
}
{
  "data": {
    "resource": {
      "id": "TK123"
    },
    "context": {
      "orgId": "OR123",
      "actorId": "US123",
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "activityId": "AC123",
      "phoneNumberGroupId": null
    },
    "links": { "quo": "https://my.quo.com/inbox/PN123/c/CN123?taskId=TK123" }
  }
}

See also