Channel Manager
Use Channel Manager with your own application. Find setup requirements, credentials, messaging and voice APIs, and the differences between channels.
Channel Manager connects your application to phone calls, SMS/MMS, email, iMessage, and WhatsApp. With the standalone API, your team configures and runs the agent. Channel Manager provides the provisioned senders, channel APIs, and delivery records.
Your application owns the conversation, consent decisions, scheduling, and business records. You can use Channel Manager without a Concurrence workspace, agent, service, or Developer Console. Start with Integrate Channel Manager Through the API.
If you use a Concurrence workspace service, follow the separate service-binding path.
Webchat is separate from the standalone Channel Manager API. Your application can provide its own webchat; to embed a Concurrence Platform agent, see Serve an Agent From a Web App.
Supported Channels
Choose a channel for the intended workflow, then confirm that its sender and use case are approved. Access to an API does not establish permission to run a particular outreach program.
SMS/MMS
Assigned number, required sender registration, and recipient consent
SMS replies do not identify a specific outbound message. MMS also needs verified content access.
Verified sending domain, sender, inbound_mode (receiving modes also need verified inbound MX), and unsubscribe configuration
Replies can carry a parent message reference; bodies and attachments need authenticated retrieval.
iMessage
Assigned sending lines and an approved recipient
Delivery may fall back to SMS. Check the reported service and downgrade result.
Provisioned business number and a recipient who has contacted the use case
The current send API accepts free-form replies within 23 hours of an inbound message. It does not send templates.
Inbound/outbound voice
Assigned number, voice event receiver, and a compatible audio endpoint
REST controls the call and participants. Audio uses a separate connection to your runtime.
Provision a Channel
Request the channel through your Concurrence implementation contact. Describe the intended sender, recipients, purpose, and sample content. Your contact will confirm which sender registrations and channel-provider approvals are required.
Before using the route, get confirmation that the required approvals are complete and the sender and use case are ready. Submitting a request or obtaining API credentials does not approve the channel or provision it automatically.
Ask your Concurrence implementation contact for the setup ID, use-case ID, assigned sender, and setup-scoped client credentials. A setup is the access boundary for channel resources. A use case identifies the provisioned purpose, channel, and routing configuration within it.
For messaging, provide an HTTPS receiver and obtain its signing secret. For voice, agree the call-event and audio connection contracts as well. Confirm the API host, processing region, retention, and support responsibilities before handling customer data. See Program Readiness.
Standalone API
Use Channel Manager-issued IDs to identify resources in your application, API requests, and support records. Save message IDs alongside your own workflow ID; do not depend on provider identifiers.
The current public base URL is https://channels.api-us.concurrence.com. Its OpenAPI schema and full interactive reference are separate from the workspace Platform API. Use the host assigned during onboarding. Using this public endpoint does not mean Channel Manager runs in your cloud account; that deployment requires its own agreement and acceptance.
Keep the host in configuration. Change it only when you receive an explicit API migration notice; a website or email-domain change does not change API URLs or callback destinations.
Authenticate
Exchange the provisioned client ID and secret for a bearer token. Tokens last one hour; there is no refresh token. Request the scopes needed for your channel and check the returned scope: ungranted scopes are omitted. Access is restricted by both operation scope and setup ID.
Keep client secrets on your server. Use separate credentials for runtime traffic and administration. Rotating a client secret prevents new token exchanges with that secret; issued tokens can remain valid until expiry.
Exchange client credentials for a 1-hour access token (client_credentials grant).
Authenticate via HTTP Basic or client_id/client_secret in the form body; the requested scopes are intersected down to the client's grant. No refresh token.
Space-delimited requested scopes (RFC 6749 §3.3); down-scoped to the grant.
Successful Response
Missing client credentials.
Invalid client credentials.
Malformed token request (e.g. unsupported grant_type).
POST /v1/oauth/token HTTP/1.1
Authorization: Basic username:password
Content-Type: application/x-www-form-urlencoded
Accept: */*
Content-Length: 78
"grant_type='text'&scope='text'&client_id='text'&client_secret='text'"{
"access_token": "text",
"token_type": "text",
"expires_in": 1,
"scope": "text"
}Inspect Provisioned Use Cases
List the use cases in your assigned setup, then read the selected use case. Check its channel, sender configuration, and receiver URL against the provisioning handoff. For phone-based channels, use the detailed read to inspect assigned numbers; the collection response can leave phone_numbers empty.
List a setup's use cases, optionally filtered by entity, channel, or deletion state.
Requires the use-case:read OAuth2 scope on the setup given by the setup_id query parameter.
Filter to use cases bound to this setup.
Filter to use cases belonging to this entity.
Filter by channel.
Include soft-deleted use cases (audit trail). Defaults to live only.
falseSuccessful Response
Query parameter failed validation.
GET /v1/use-case?setup_id=123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"items": [
{
"id": "text",
"entity_name": "text",
"name": "text",
"description": "text",
"environment": "staging",
"region": "us-east-1",
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z",
"deleted_at": "2026-01-01T00:00:00.000Z",
"channel": "text",
"twilio_setup_id": "text",
"inbound_voice_url": "text",
"webhook_url": "text",
"allowed_forwarding_numbers": [
"text"
],
"phone_numbers": [
"text"
]
}
]
}Fetch one use case with its channel binding. Tombstoned rows return 404.
Requires the use-case:read OAuth2 scope on the target setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this use case.
Use case not found (or soft-deleted).
Validation Error
GET /v1/use-case/{use_case_id} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"id": "text",
"entity_name": "text",
"name": "text",
"description": "text",
"environment": "staging",
"region": "us-east-1",
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z",
"deleted_at": "2026-01-01T00:00:00.000Z",
"channel": "text",
"twilio_setup_id": "text",
"inbound_voice_url": "text",
"webhook_url": "text",
"allowed_forwarding_numbers": [
"text"
],
"phone_numbers": [
"text"
]
}Send and Reconcile SMS
Save the Channel Manager message id with your workflow ID. Use it for message and attachment reads. A successful send response means the provider accepted the message. Read the message again for later delivery or error information.
The send operation has no client-supplied idempotency key. After a timeout, reconcile the attempt before resending. When listing messages, supply setup_id and at least one of use_case_id or peer_phone_number.
Send an SMS or MMS from one of the use case's numbers.
A2P and toll-free senders only reach recipients who've opted in, and never anyone who's opted out.
Requires the sms:send OAuth2 scope on the target setup.
[]Successful Response
NULL at INSERT — when sending via messaging_service_sid Twilio doesn't
pick a sender until queueing; the queued callback backfills it.
NULL until sent callback — Twilio computes segments at carrier handoff.
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this use case.
SMS use case not found.
Request rejected: no phone number assigned, no verified A2P campaign for a US long-code sender pool, no recorded opt-in for a US/CA toll-free or US long-code sender pool, message content does not match the registered use case, invalid recipient, recipient opted out, destination region not permitted, or A2P brand suspended.
POST /v1/sms/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: multipart/form-data
Accept: */*
Content-Length: 125
{
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"to_phone_number": "text",
"message": "text",
"media_attachments": [
"text"
]
}{
"id": "123e4567-e89b-12d3-a456-426614174000",
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"from_number": "text",
"to_number": "text",
"body": "text",
"num_segments": 1,
"status": "pending",
"attachment_names": [
"text"
],
"handed_off_at": "2026-01-01T00:00:00.000Z",
"delivered_at": "2026-01-01T00:00:00.000Z",
"errored_at": "2026-01-01T00:00:00.000Z",
"error_code": 1,
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}Fetch one SMS or MMS by id, inbound or outbound.
Requires the sms:read OAuth2 scope on the target setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
SMS message not found in either the inbound or outbound table.
Validation Error
GET /v1/sms/{message_id} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"direction": "outbound",
"id": "123e4567-e89b-12d3-a456-426614174000",
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"from_number": "text",
"to_number": "text",
"body": "text",
"num_segments": 1,
"status": "pending",
"attachment_names": [
"text"
],
"handed_off_at": "2026-01-01T00:00:00.000Z",
"delivered_at": "2026-01-01T00:00:00.000Z",
"errored_at": "2026-01-01T00:00:00.000Z",
"error_code": 1,
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}List inbound + outbound SMS messages, interleaved chronologically. At least one of use_case_id or peer_phone_number is required so the query stays on a leading index per leg. Optional direction=inbound|outbound prunes one leg. Sort via repeated sort_by query params; each entry is +field or -field (allowed field: created_at). Default: -created_at. Cursor opaque — round-trip continuation_token unchanged with the same sort_by.
Sort axes, applied left-to-right. +field ascending, -field
descending. Defaults to [("created_at", -1)] (newest first).
[]The maximum number of SMS messages to return.
50Opaque continuation token from a prior response. Treat as a black box — the server decodes it.
Successful Response
Query parameter failed validation, neither use_case_id nor peer_phone_number was supplied, or continuation_token is unparseable.
GET /v1/sms?setup_id=123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"items": [
{
"direction": "outbound",
"id": "123e4567-e89b-12d3-a456-426614174000",
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"from_number": "text",
"to_number": "text",
"body": "text",
"num_segments": 1,
"status": "pending",
"num_attachments": 1,
"handed_off_at": "2026-01-01T00:00:00.000Z",
"delivered_at": "2026-01-01T00:00:00.000Z",
"errored_at": "2026-01-01T00:00:00.000Z",
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}
],
"has_more": true,
"continuation_token": null
}MMS uses the same send operation with attachments. Test retrieval from your application before enabling inbound media.
Redirect to a presigned URL for one MMS attachment, the retained private copy.
The name must be listed on the row, so a caller can't sign a URL for an arbitrary key. 404 if the message or name is unknown.
Requires the sms:read OAuth2 scope on the target setup.
Successful Response
Redirect to a short-lived S3 presigned URL for the attachment.
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
SMS message not found, or no attachment with this name on the message.
Validation Error
GET /v1/sms/{sms_id}/attachment/{attachment_name} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
No content
SMS Recipient Opt-In and Opt-Out
Recipient controls use separate setup-scoped permissions: sms:consent:read to read opt-outs and sms:consent:write to send an opt-in message or clear a setup-wide opt-out. A workspace API key or an sms:send grant alone does not authorize these operations. Supply recipient phone numbers in E.164 format.
Send the registered opt-in message. Supply use_case_id and to_phone_number. Channel Manager selects the confirmation copy and sender from the use case's verified campaign or approved US/Canada toll-free registration. This is required before ordinary sends on the supported US long-code and US/Canada toll-free paths. Your application must already hold the appropriate consent evidence.
The operation returns 201 without a message resource ID. Once the recipient has an opt-in record for that use case, repeating it is a no-op. It returns 409 for an active setup-wide opt-out; sending an opt-in message cannot reverse a STOP. The record establishes acceptance of the confirmation send, not receipt or proof of consent.
Send the registered consent message and record the recipient's opt-in.
Copy and sending line come from the use case's verified A2P campaign or approved toll-free verification; 422 if neither backs it. Idempotent per recipient. 409 when the recipient has a live opt-out — an opt-in can't reverse a STOP.
Requires the sms:consent:write OAuth2 scope on the target setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this use case.
SMS use case not found.
The recipient has a live setup-wide opt-out (a prior STOP); an opt-in cannot reverse it.
Neither a verified A2P campaign nor an approved US/CA toll-free verification backs the SMS use case, or Twilio rejected the destination (invalid / landline / opted out / region not permitted / brand suspended).
POST /v1/sms/opt-in HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 79
{
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"to_phone_number": "text"
}No content
Read opt-outs. Supply setup_id, with optional user_phone_number to check one recipient. Results contain active setup-wide suppressions by default; include_deleted=true includes cleared records and their reversal reasons. Use the returned CM record and message IDs for correlation. An empty result is not proof of consent or of clearance on every sending line.
List setup-wide SMS opt-outs (STOP records) for the setup given by the required setup_id query parameter. Live rows only by default; pass include_deleted=true for the cleared/audit history. Optional user_phone_number exact-match filter. Sort via repeated sort_by query params; each entry is +field or -field (allowed field: created_at). Default: -created_at. Cursor opaque — round-trip continuation_token unchanged with the same sort_by.
Optional exact-match filter — the opted-out recipient's E.164 number.
Include cleared (soft-deleted) opt-outs. Default False → live rows only.
falseSort axes, applied left-to-right. +field ascending, -field
descending. Defaults to [("created_at", -1)] (newest first).
[]The maximum number of opt-outs to return.
50Opaque continuation token from a prior response. Treat as a black box — the server decodes it.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
A query parameter failed validation, or continuation_token is unparseable.
GET /v1/sms/opt-out?setup_id=123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"items": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"setup_id": "123e4567-e89b-12d3-a456-426614174000",
"phone_number": "text",
"opted_out_use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"opted_out_message_id": "123e4567-e89b-12d3-a456-426614174000",
"created_at": "2026-01-01T00:00:00.000Z",
"deleted_at": "2026-01-01T00:00:00.000Z",
"delete_reason": "text"
}
],
"has_more": true,
"continuation_token": null
}Clear a setup-wide opt-out. An authorized caller supplies setup_id, user_phone_number, and a non-empty reason after reviewing renewed consent. Success returns 204 and retains the cleared record. The operation returns 404 if no active setup-wide opt-out exists, or 409 while a toll-free line under the setup still has an active opt-out for that recipient. The recipient must text START or UNSTOP to the affected toll-free line first.
Operator-clear a setup-wide SMS opt-out, recording the reason for audit.
The only way to re-enable sending after a STOP. 409 while a live toll-free opt-out for the recipient remains: route them through START/UNSTOP first.
Requires the sms:consent:write OAuth2 scope on the target setup.
Required, non-empty: this is the audit record of why the STOP was reversed.
Opt-out cleared; the recipient can be sent to again on this setup.
No content
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
No live setup-wide opt-out exists for this setup and phone number.
A live toll-free opt-out exists for this recipient under the setup; clear it via a recipient-initiated START/UNSTOP first.
Validation Error
POST /v1/sms/remove-setup-opt-out HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 94
{
"setup_id": "123e4567-e89b-12d3-a456-426614174000",
"user_phone_number": "text",
"reason": "text"
}No content
Recipients opt out through the supported SMS keywords. The current public API exposes opt-out reads and reversal; it does not expose an operation to create an opt-out directly.
Send and Reconcile Email
Send inline HTML or use a stored template. For an unsubscribable use case, inline HTML must reference both unsubscribe_link and unsubscribe_from_all_link; Channel Manager supplies the URLs. For a non-unsubscribable use case, inline HTML must contain no template variables. Stored templates have their own declared parameters.
Save the returned Channel Manager email id. Use it for email reads and as email_id when reading events; the event query also requires setup_id. These reads expose delivery, bounce, and complaint information. Treat opens and clicks as tracking signals, not proof of an outcome.
When replying to an inbound email, supply its Channel Manager ID as reply_to_inbound_email_id. It must belong to the same setup and use case. The send path has no client-supplied idempotency key, so reconcile uncertain sends before retrying.
To correlate an inbound reply, read the email using the callback's inbound_message_ref. The returned in_reply_to_email_id identifies the outbound CM email it replies to, or is null for an unthreaded message. Use these CM IDs for threading.
Send one email through SES, from an inline HTML body or a stored template.
raw_html mode needs subject and body_html; template mode needs template_id and template_parameters covering every declared variable. Unsubscribe links are injected by the send path, never the caller. The use case's setup must be DNS-verified.
Requires the email:send OAuth2 scope on the target setup.
[]Successful Response
Our row id (also passed to SES as EmailTag.amigo_email_id).
When SendEmail returned and the row landed.
Filenames in send-time order, matching each attachment's S3 key suffix.
The inbound row's id when this send replied to one of our inbound emails; NULL otherwise.
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this use case.
Use case not found, or has no live SES binding.
Recipient is on the per-use-case or per-setup unsubscribe list.
Body or any attachment exceeds the size limit (5 MiB per attachment).
Body or attachment failed validation: invalid HTML template, invalid filename, empty attachment, more than 5 attachments, SES rejected the message, or reply_to_inbound_email_id does not reference an inbound email under the same use case + setup.
POST /v1/email/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: multipart/form-data
Accept: */*
Content-Length: 310
{
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"to_address": "name@gmail.com",
"mode": "template",
"template_parameters": "text",
"template_id": "123e4567-e89b-12d3-a456-426614174000",
"subject": "text",
"body_html": "text",
"attachments": [
"text"
],
"reply_to_inbound_email_id": "123e4567-e89b-12d3-a456-426614174000"
}{
"id": "123e4567-e89b-12d3-a456-426614174000",
"created_at": "2026-01-01T00:00:00.000Z",
"attachment_filenames": [
"text"
],
"replied_to_inbound_email_id": "123e4567-e89b-12d3-a456-426614174000"
}Fetch one email by id, inbound or outbound.
Requires the email:read OAuth2 scope on the target setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
Email not found.
Validation Error
GET /v1/email/{email_id} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"direction": "text",
"id": "123e4567-e89b-12d3-a456-426614174000",
"ses_use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"setup_id": "123e4567-e89b-12d3-a456-426614174000",
"from_address": "text",
"to_address": "text",
"subject": "text",
"received_at": "2026-01-01T00:00:00.000Z",
"created_at": "2026-01-01T00:00:00.000Z",
"in_reply_to_email_id": "123e4567-e89b-12d3-a456-426614174000",
"attachment_filenames": [
"text"
],
"body_kind": "html"
}List SES events matching the request filters. At least one of use_case_id or setup_id is required so the underlying emails join lands on a leading index. Other filters (email_id, event_type, event_at_after, event_at_before) narrow within that scope. Sort via repeated sort_by query params; each entry is +field or -field (allowed field: event_at). Default: -event_at. Cursor opaque — round-trip continuation_token unchanged with the same sort_by.
Filter to events for emails sent through this use case.
Filter to events for emails sent from this SES setup.
Narrow to one or more emails' event timelines (set semantics: any match).
[]Inclusive lower bound on event_at.
Exclusive upper bound on event_at.
Sort axes applied left-to-right; +field/-field. Default -event_at (newest first).
[]The maximum number of events to return.
50Opaque continuation token from a prior response; treat as a black box.
Successful Response
Whether there are more events to retrieve.
Opaque token to pass back on the next page request; null when no more results.
Query parameter failed validation, neither use_case_id nor setup_id was supplied, or continuation_token is unparseable.
GET /v1/email/event?setup_id=123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"items": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"email_id": "123e4567-e89b-12d3-a456-426614174000",
"event_type": "Reject",
"event_at": "2026-01-01T00:00:00.000Z",
"created_at": "2026-01-01T00:00:00.000Z"
}
],
"has_more": true,
"continuation_token": null
}Sending requires the email setup's sending DNS records (DKIM, MAIL FROM, and DMARC) to be verified; otherwise the send returns 409. The inbound MX record is not needed to send. Whether a use case receives email at all depends on its inbound_mode (send_only, replies_only, or all_inbound), shown on the use-case read; a send_only use case drops inbound email, including replies. Changing a send_only use case to a receiving mode returns 409 until inbound MX is verified. See Email for the modes.
Inbound bodies and attachments need a separate read. Confirm that the configured content URLs are accessible from your deployment before relying on email replies.
Stream one email's body from S3, inbound or outbound.
Content-Type follows the stored body kind (HTML or plain text).
Requires the email:read OAuth2 scope on the target setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
Email not found.
Validation Error
GET /v1/email/{email_id}/body HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
No content
Redirect to a presigned URL for one email attachment.
The filename must be listed on the row, so a caller can't sign a URL for an arbitrary key under the email's S3 prefix. 404 if the email or filename is unknown.
Requires the email:read OAuth2 scope on the target setup.
Successful Response
Redirect to a short-lived S3 presigned URL for the attachment.
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
Email not found, or attachment with this filename does not exist on the email.
Validation Error
GET /v1/email/{email_id}/attachment/{filename} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
No content
Send and Reconcile iMessage
Send text, one media file, or both within the operation’s limits. Before the recipient replies to the selected line, sends are text-only within the limits below. The service prefers a pool line the contact has replied to, then the most recently used line, then another available line. Delivery can fall back to SMS; do not use this route when your program requires iMessage-only delivery. Read the message's service, was_downgraded, and delivery/error fields after sending.
Replies do not carry a parent outbound reference. Use your own session mapping, as described in the receiver walkthrough. Reconcile an uncertain send before retrying; this endpoint has no client-supplied idempotency key.
Send one iMessage from the use case's pool, sticky per contact.
At least one of content or media is required, one media file at most. 422 when the pool is empty, a pre-reply limit applies, or SendBlue rejects the send.
Requires the imessage:send OAuth2 scope on the target setup.
""Successful Response
An outbound row projection, plus terminal-delivery columns.
Row id; also the S3 key prefix imessage/{id}/{filename} for media.
Top-level channel.use_cases.id, not the SendBlue-side id.
E.164 sender (outbound: our line; inbound: the foreign sender).
E.164 recipient (outbound: the foreign recipient; inbound: our line).
Text content. Empty string for media-only messages.
True when media is stored; fetch via GET /v1/imessage/{id}/media.
SendBlue's service classification; NULL on outbound until the terminal-delivery webhook lands.
Send time (outbound) or receive time (inbound); list orders by this DESC. Not a carrier delivery time.
outboundSet on the first terminal DELIVERED / SENT webhook. NULL until then.
Set on the first terminal ERROR / DECLINED webhook; mutually exclusive with delivered_at.
Free-text SendBlue failure description. NULL when not errored.
True when SendBlue downgraded iMessage → SMS; NULL until the terminal-delivery webhook lands.
When SendBlue accepted and sent the message.
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this use case.
Use case not found, or has no live SendBlue binding.
media file exceeds the 20 MiB limit.
Body / media failed validation: empty content + no media, invalid filename, empty media file, no live SendBlue lines available, a SendBlue before-the-first-reply limit (message count, length, media, or a link / phone number / email address), or SendBlue rejected the send.
POST /v1/imessage/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: multipart/form-data
Accept: */*
Content-Length: 101
{
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"to_number": "text",
"content": "",
"media": "text"
}{
"id": "123e4567-e89b-12d3-a456-426614174000",
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"from_number": "text",
"to_number": "text",
"content": "text",
"has_media": true,
"service": "iMessage",
"occurred_at": "2026-01-01T00:00:00.000Z",
"direction": "outbound",
"delivered_at": "2026-01-01T00:00:00.000Z",
"errored_at": "2026-01-01T00:00:00.000Z",
"error_message": "text",
"was_downgraded": true,
"sent_at": "2026-01-01T00:00:00.000Z"
}Fetch one iMessage by id, inbound or outbound.
Requires the imessage:read OAuth2 scope on the target setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
iMessage not found in either the inbound or outbound table.
Validation Error
GET /v1/imessage/{message_id} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"from_number": "text",
"to_number": "text",
"content": "text",
"has_media": true,
"service": "iMessage",
"occurred_at": "2026-01-01T00:00:00.000Z",
"direction": "inbound"
}Redirect to a presigned URL for an iMessage's media. 404 if the message carries none.
Requires the imessage:read OAuth2 scope on the target setup.
Successful Response
Redirect to a short-lived (5-minute) S3 presigned URL for the media artifact.
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
iMessage not found, or the row exists but carries no media artifact.
Validation Error
GET /v1/imessage/{message_id}/media HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
No content
Before a contact’s first reply to the selected line, messages are limited to 300 characters, cannot include media, links, phone numbers, or email addresses, and stop after six recorded sent/delivered messages. A pre-reply restriction returns 422; a content-check failure does not silently authorize the send. Setup-wide iMessage opt-outs also block sends with 422; the recipient can text START to opt back in. These controls are separate from SMS recipient controls.
Reply Through WhatsApp
Use the business-scoped recipient ID (BSUID) from the inbound event's caller_ref, not a phone number. The recipient must have sent a message to this use case within the last 23 hours. The current send operation supports free-form messages; the presence of a template-creation API does not provide template sending or cold outreach through this route.
Save the returned message_id, which is a Channel Manager-issued message ID. The current public schema has no WhatsApp message-read or delivery-event-list operation. Before launch, ask your channel operator how to investigate delivery and uncertain sends. The messaging receiver does not provide a delivery-event feed.
Send a WhatsApp message from a use case's number to a BSUID-addressed recipient.
Free-form only (no templates): the recipient must have messaged this use case
within 23 hours (one hour inside Meta's 24, so an accepted send can't die at the
window edge). Requires the whatsapp:send OAuth2 scope on the target setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this setup.
WhatsApp use case not found, or target message not found for this recipient.
Request failed validation, or the send was rejected: no inbound message from this recipient in the last 23 hours, Meta refused the media upload, the recipient cannot receive the message, Meta rejected a parameter, or a reaction removal targeted a message with no reaction.
POST /v1/whatsapp/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/x-www-form-urlencoded
Accept: */*
Content-Length: 170
"use_case_id='123e4567-e89b-12d3-a456-426614174000'&to_bsuid='text'&message_type='text'&message='text'&target_message_id='123e4567-e89b-12d3-a456-426614174000'"{
"message_id": "123e4567-e89b-12d3-a456-426614174000"
}Control Voice Calls
Voice separates call control from audio. Creating a call reserves a call reference and sender number; it does not dial anyone. Add a media participant to connect your runtime's wss:// audio endpoint, and a dial participant to call the recipient. The audio endpoint must implement the provisioned media protocol; a generic agent WebSocket is not sufficient.
Participant creation returns before the person answers or joins. Read the call and participants to track those transitions. Keep call_ref and each participant_ref with your application's session ID. Ending a connected call requests teardown; read it again to confirm ended_at.
These APIs also support human participants and participant redirection. Design and test the handoff with your voice integration owner before using it with customers. Confirm recording, retention, and disclosure requirements during provisioning.
Deleting a participant removes that leg, including canceling a still-ringing destination. A leg already recorded as having left returns 204; an unknown call or participant remains 404. Until the conference reference is available, deletion returns 409, so retry after checking call state rather than assuming removal.
Remove a participant from the conference. voice:call on the setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks voice:call scope / access to this setup.
Call or participant not found.
Validation Error
DELETE /v1/voice-call/{call_ref}/participant/{participant_ref} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
No content
Create a call resource and select its From number. voice:call on the setup.
Outbound-voice use case whose sender pool the call is placed from.
Retry-dedup key; a repeat returns the existing call rather than a second one.
Successful Response
The From number selected from the use case's pool.
Missing, expired, or invalid bearer token.
Token lacks voice:call scope / access to this setup.
Use case not found (or not outbound_voice), or has no assigned phone numbers.
Validation Error
POST /v1/voice-call/ HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 79
{
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"idempotency_key": "text"
}{
"call_ref": "123e4567-e89b-12d3-a456-426614174000",
"phone_number": "text"
}Dial or media-stream a leg into the call's conference. voice:call on the setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks voice:call scope / access to this setup.
Call not found.
Call has already ended.
Twilio rejected the dial target, or an operator / transfer_target leg targets a number not in the use case's allowed_forwarding_numbers.
POST /v1/voice-call/{call_ref}/participant HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 103
{
"kind": "media",
"stream_url": "https://example.com",
"stream_context": {
"ANY_ADDITIONAL_PROPERTY": "text"
}
}{
"participant_ref": "123e4567-e89b-12d3-a456-426614174000",
"role": "caller",
"provider_call_sid": "text"
}Return one call and its participants. voice:read on the call's setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks voice:read scope / access to this setup.
Call not found.
Validation Error
GET /v1/voice-call/{call_ref} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"call_ref": "123e4567-e89b-12d3-a456-426614174000",
"use_case_id": "123e4567-e89b-12d3-a456-426614174000",
"setup_id": "123e4567-e89b-12d3-a456-426614174000",
"direction": "inbound",
"phone_number": "text",
"peer_number": "text",
"recording_available": true,
"created_at": "2026-01-01T00:00:00.000Z",
"started_at": "2026-01-01T00:00:00.000Z",
"ended_at": "2026-01-01T00:00:00.000Z",
"participants": [
{
"participant_ref": "123e4567-e89b-12d3-a456-426614174000",
"kind": "inbound",
"role": "caller",
"to_phone_number": "text",
"dial_status": "answered",
"answered_by": "human",
"created_at": "2026-01-01T00:00:00.000Z",
"answered_at": "2026-01-01T00:00:00.000Z",
"joined_at": "2026-01-01T00:00:00.000Z",
"left_at": "2026-01-01T00:00:00.000Z"
}
]
}End the call's conference and stamp ended_at. voice:call on the setup.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks voice:call scope / access to this setup.
Call not found.
Validation Error
DELETE /v1/voice-call/{call_ref} HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
No content
The participant media-stream operation below forks that participant's incoming audio in one direction. It is separate from the media participant used to connect the agent's audio. Start it only after the participant has joined and before it leaves; either an unjoined or departed participant returns 409, as does a repeated start. If the participant leg has already ended by the time the fork is attempted - for example when the leave notification lags the recorded departure - the operation also returns 409 rather than a server error, and the once-only start guard is not consumed, so you can reconcile and retry. Supply per-fork string metadata in stream_context, which is echoed in the stream connection parameters; do not rely on URL query parameters for that context.
Fork a participant's inbound audio to a wss:// URL. voice:call on the setup.
The wss:// endpoint Twilio forks this participant's inbound audio to.
Successful Response
Missing, expired, or invalid bearer token.
Token lacks voice:call scope / access to this setup.
Call or participant not found.
Participant not yet joined, already left, or a stream is already running.
Validation Error
POST /v1/voice-call/{call_ref}/participant/{participant_ref}/media-stream HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 88
{
"stream_url": "https://example.com",
"stream_context": {
"ANY_ADDITIONAL_PROPERTY": "text"
}
}No content
Forwarding destinations. Voice use cases require approved forwarding destinations in allowed_forwarding_numbers, set when the use case is created. The list applies to dialed operator and transfer participants and to dial redirects. An empty list denies them, and a denied target returns 422 with forwarding_number_not_allowed. The ordinary outbound callee and media participants are not gated. Have the provisioning owner configure the list, then verify each transfer destination. A configured service forwarding number alone does not establish channel approval.
For inbound calls, have Concurrence configure the number's routing and your call-event receiver. The receiver starts or resumes your agent and attaches its media participant to the received call. Voice events are separate from messaging inbound-turn callbacks; the public schema does not currently define their payloads. Obtain and test the event contract during voice onboarding.
Messaging Webhook Secrets
Messaging callbacks use a per-use-case signing secret. Explicit rotation returns the new secret once and an old_secret_lasts_until boundary. During the overlap, a callback can carry signatures from both secrets. Changing the receiver URL also rotates the secret, without the same overlap. Coordinate either change with the receiver owner.
See the receiver walkthrough for signature checks, duplicate handling, and session mapping.
Rotate a messaging use case's inbound-turn HMAC signing secret.
Returns the new secret once plus the cutoff after which the old secret stops verifying (30-minute dual-sign grace). 400 for voice use cases, which have none.
Requires the use-case:write OAuth2 scope on the target setup.
Successful Response
The freshly-minted signing secret. Returned ONCE — never re-readable via the API; the caller stores it on its receiver to verify future signatures.
When the previous secret stops being accepted; until then the webhooks dual-sign both, so a receiver reconfigured before this still verifies.
Use case is a voice channel (no webhook secret).
Missing, expired, or invalid bearer token.
Token lacks the required scope or access to this use case.
Use case not found (or soft-deleted).
Validation Error
POST /v1/use-case/{use_case_id}/webhook-secret HTTP/1.1
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"webhook_secret": "text",
"old_secret_lasts_until": "2026-01-01T00:00:00.000Z"
}Service Binding
A service binding is needed when a Concurrence workspace service handles the conversation. It links the provisioned use case to that workspace and service. Rebinding can select another service in the same workspace; it cannot claim a use case already bound elsewhere.
A customer-owned application using the standalone API does not need this binding. See Use Cases & Bindings for the workspace API operations.
Last updated
Was this helpful?

