Skip to main content

Turn Channel API

The Channel API allows you to build a Channel Connector that allows you to send and receive messages from platforms other than WhatsApp. It is a REST API that builds on the Flow Interoperability Specification.

warning

This API is under very active development and is guaranteed to still change in the coming 6 months (starting from late September 2024). Please be advised to keep track of the versioning and we are guaranteeing breaking changes for at least the next 6 months.

There's no sandbox as WhatsApp but you can create multiple Channels for testing, staging or others.

Reference Implementation

We have an example implementation of a Channel Connector integrated with the Turn Channel API. You can download it here.

Creating a Channel

Channels are created using the authentication token of an existing WhatsApp number or sandbox number in your Turn.io account. This is a temporary bootstrapping problem and is going to change. Once your Channel has been created you can request an authentication token for the Channel using the Turn.io UI as you normally would.

$ curl https://whatsapp.turn.io/v1/numbers \
> -X POST \
> -H 'Content-Type: application/json' \
> -H 'Authorization: Bearer <your-token>' \
> -d '{
> "backend_type": "channel",
> "number_type": "channel_http_api",
> "from_addr": "the-address-for-your-channel",
> "name": "the display name of your channel",
> "endpoint": "https://example.org/your/webhook/endpoint"
> }'
{
"number": {
"uuid": "the-uuid-for-your-channel"
},
"webhook": {
"uuid": "the-uuid-for-your-webhook",
"hmac_secret": "the-secret-for-your-webhook"
}
}

Sending inbound messages to your Channel

When you receive an inbound message, you need to send to Turn.io Channel via our API, you need to provide a JSON object with the following fields:

  • contact: OBJECT, required - Contact object with information for the user who sent a message to the Channel, contact details are updated with this information.
  • message: OBJECT, required - Information about a message received by the Channel.

Parameters for the contact object

NameRequiredDescription
idyesString. The use ID.
profileyesObject. A user profile object. Profile objects have the following properties: name - String. The user's name.

Parameters for the message object

The message object is nested within the value object and should be sent when a user sends a message using the Channel.

NameRequiredDescription
typeyesString. The type of message that has been received by the Channel. Possible values can be one of the following: text, video, audio, button, image, document, sticker, interactive.
audionoObject. When message type is set to audio, this object must be included. Audio objects have the following properties:
- id - String. ID for the audio file.
- mime_type - String. Mime type of the audio file.
documentnoObject. When message type is set to document, this object must be included. Document objects have the following properties:
- id - String. ID for the document file.
- mime_type - String. Mime type of the document file.
- link - String. Link to the document file.
- sha256 - String. SHA256 hash of the document file.
- filename - String. Filename of the document file.
- caption - String (optional). Caption of the document file.
videonoObject. When message type is set to video, this object must be included. Video objects have the following properties:
- id - String. ID for the video file.
- mime_type - String. Mime type of the video file.
- link - String. Link to the video file.
- sha256 - String. SHA256 hash of the video file.
- filename - String. Filename of the video file.
- caption - String (optional). Caption of the video file.
imagenoObject. When message type is set to image, this object must be included. Image objects have the following properties:
- id - String. ID for the image file.
- mime_type - String. Mime type of the image file.
- link - String. Link to the image file.
- sha256 - String. SHA256 hash of the image file.
- caption - String (optional). Caption of the image file.
textnoObject. When message type is set to text, this object must be included. Text objects have the following properties:
- body - String. The text of the message.
stickernoObject. When message type is set to sticker, this object must be included. Sticker objects have the following properties:
- id - String. ID for the sticker file.
- mime_type - String. Mime type of the sticker file.
- link - String. Link to the sticker file.
- sha256 - String. SHA256 hash of the sticker file.
- filename - String. Filename of the sticker file.
- caption - String (optional). Caption of the sticker file.
interactivenoObject. When message type is set to interactive, this object must be included. It should be used when a user has interacted with your message. Interactive objects have the following properties:
- type - Object with the following properties:
  • button_reply - Sent when a user clicks a button. Object with the following properties:
    • id - String. Unique ID of a button.
    • title - String. Title of a button.
  • list_reply - Sent when a user selects an item from a list. Object with the following properties:
    • id - String. Unique ID of the selected list item.
    • title - String. Title of a the selected list item.
    • description - String. Description of the selected row.
fromyesString. The user ID. A Channel can respond to a user using this ID.
idyesString. The ID for the message that was received by the Channel.
timestampyesString. Unix timestamp indicating when received the message from the user.

Text

$ curl -X POST https://whatsapp.turn.io/v1/numbers/eb2828fa-7605-4c97-bba1-ea74e25c7452/messages \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"contact": {
"id": "the-user-id",
"profile": {
"name": "the-user-name"
}
},
"message": {
"type": "text",
"text": {
"body": "the-text-of-the-message"
},
"from": "the-user-id",
"id": "the-message-id",
"timestamp": "1727291610"
}
}'
> {"success": true}

Document

{
"message": {
"from": "1234567890",
"id": "message_id_789",
"type": "document",
"document": {
"id": "document_id_1",
"mime_type": "application/pdf",
"link": "https://example.org/document.pdf",
"sha256": "hash_value",
"filename": "the-filename.pdf"
},
"timestamp": "1617183833"
},
"contact": {
"profile": {
"name": "Charlie Brown"
}
}
}

Video

{
"contact": {
"id": "the-user-id",
"profile": {
"name": "the-user-name"
}
},
"message": {
"type": "video",
"video": {
"link": "https://example.org/video.mp4",
"mime": "video/mp4",
"id": "video",
"sha256": "hash_value",
"filename": "the-filename.mp4"
},
"from": "the-user-id",
"id": "the-message-id",
"timestamp": "1727291610"
}
}

Audio

{
"contact": {
"id": "the-user-id",
"profile": {
"name": "the-user-name"
}
},
"message": {
"type": "audio",
"audio": {
"link": "https://example.org/audio.mp3",
"mime": "audio/mp3",
"id": "audio"
},
"from": "the-user-id",
"id": "the-message-id",
"timestamp": "1727291610"
}
}

Image

{
"message": {
"from": "1234567890",
"id": "message_id_456",
"type": "image",
"image": {
"id": "image_id_789",
"mime_type": "image/jpeg",
"link": "https://example.org/image.jpg",
"sha256": "hash_value",
"caption": "Beautiful sunset"
},
"timestamp": "1617183830"
},
"contact": {
"profile": {
"name": "Jane Smith"
}
}
}

Sticker

{
"message": {
"from": "1234567890",
"id": "message_id_789",
"type": "sticker",
"sticker": {
"id": "sticker_id_1",
"mime_type": "image/png",
"link": "https://example.org/sticker.png",
"sha256": "hash_value",
"filename": "the-filename.png",
"caption": "Beautiful sunset"
},
"timestamp": "1617183831"
},
"contact": {
"profile": {
"name": "Bob Johnson"
}
}
}

Interactive Button Reply

{
"message": {
"from": "1234567890",
"id": "message_id_789",
"type": "interactive",
"interactive": {
"type": "button_reply",
"button_reply": {
"id": "button_id_1",
"title": "Yes"
}
},
"timestamp": "1617183831"
},
"contact": {
"profile": {
"name": "Bob Johnson"
}
}
}

Interactive List Reply

{
"message": {
"from": "1234567890",
"id": "message_id_789",
"type": "interactive",
"interactive": {
"type": "list_reply",
"list_reply": {
"id": "list_id_1",
"title": "Option 1",
"description": "Description for option 1"
}
},
"timestamp": "1617183832"
},
"contact": {
"profile": {
"name": "Alice Johnson"
}
}
}

Error Response

{
"errors": {
"contact": {
"id": [
"can't be blank"
],
"profile": [
"can't be blank"
]
},
"message": {
"from": [
"can't be blank"
],
"id": [
"can't be blank"
],
"timestamp": [
"can't be blank"
],
"type": [
"can't be blank"
]
}
},
"message": "Bad Request"
}

Receiving outbound messages from your Channel

When Turn.io needs to submit a message to a subscriber on your Channel, it will notify you via the webhook endpoint supplied when creating the channel.

The JSON payload is standardised as follows:

{
"to": "the-address-of-the-recipient",
"version": "0.0.1-alpha",
// the FLOIP block for the current step in the journey
"block": {...},
// the context of the Journey at the time of this step
"context": {...},
// the list of FLOIP resources required for rendering the block configuration
// against the given context
"resources": [...],
// the FLOIP resources above but evaluated against the given context using
// Turn.io's Expression library
"evaluated_resources": [...],
// Turn.io's evaluated response for the current block step for your
// own reference. At times it may be quite a bit easier to use this
// for message delivery via the channel as the FLOIP block and resource
// evaluation will likely take quite a bit more developer effort
"turn": {...}
}

The specifications for the turn payloads are outlined below.

tip

All media assets are supplied as signed URLs that are valid for 2 minutes.

The Channel connector will receive the above JSON payload via HTTP and it will be signed with the Channel's HMAC Secret. It is the implementer's responsibility to validate the signature against the payload to prevent rogue access to the API endpoint.

The Channel Connector endpoint must respond with an HTTP 200 response, the application/json Content-Type HTTP header and the following JSON body

{
"messages": [{"id": "the-channel-message-id"}]
}

Turn.io will store this message id as a reference for the message delivered and any status updates on routing or delivery there-of must refer to this message id for correct processing and storage.

Text Message

This is a plain text message, which can include Markdown formatting and Emojis.

{
"type": "text",
"text": {
"body": "the evaluated message"
}
}

Content Message

This is a Content Card from the Turn.io UI sent as a message.

info

If the content card has media attachments, they're not included at the moment. These are sent as regular text messages.

The FLOIP configuration block as all the details of the card however.

{
"type": "text",
"text": {
"body": "the evaluated message"
}
}

Template Message

This is a WhatsApp template message needing to be sent. Channel Connector implementers will need to decide how to best represent this message on the destination channel. All the information available for the template itself is included in the FLOIP block configuration.

{
"template": {
"components": [
{
"parameters": [
{
"text": "Jane",
"type": "text"
}
],
"type": "body"
}
],
"language": {
"code": "en"
},
"name": "hello_template",
"namespace": "message_template_namespace_10"
}
}

Interactive Message

These are messages that are either quick reply buttons, list buttons, or WhatsApp flow messages. Below are the details for each.

Quick Reply Buttons

{
"type": "interactive",
"interactive": {
"action": {
"buttons": [
{
"reply": {
"id": "QmxhY2sgQ29mZmVl",
"title": "Black Coffee"
},
"type": "reply"
},
{
"reply": {
"id": "QmxhY2sgVGVh",
"title": "Black Tea"
},
"type": "reply"
}
]
},
"body": {
"text": "Please select an order"
},
"type": "button"
}
}

List Button

{
"type": "interactive",
"interactive": {
"action": {
"button": "Option",
"sections": [
{
"rows": [
{
"id": "8J+YgCBPbmU=",
"title": "😀 One"
}
]
}
]
},
"body": {
"text": "Hello this is my first text."
},
"type": "list"
}
}

WhatsApp Flow

{
"interactive": {
"action": {
"name": "flow",
"parameters": {
"flow_action": "navigate",
"flow_action_payload": {
"screen": "BOOKING"
},
"flow_cta": "Make a booking!",
"flow_id": "1271481943527749",
"flow_message_version": "3",
"flow_token": "3b523386-adbd-4715-b977-7e292aaf57c3",
"mode": "published"
}
},
"body": {
"text": "Please pick a convenient date and time and we'll complete the booking."
},
"footer": {
"text": "This is a demo. No real booking will be made."
},
"header": {
"text": "Make a booking",
"type": "text"
},
"type": "flow"
},
"type": "interactive"
}

Video Message

{
"type": "video",
"video": {
"caption": "hello world",
"link": "https://whatsapp.turn.io/uploads/2024/09/24/26954/9fa7b372-faf5-483f-a82a-61c0129d50fe",
"mime_type": "video/mp4"
}
}

Audio Message

{
"audio": {
"link": "https://whatsapp.turn.io/uploads/2024/09/24/27038/2f9f3c3f-f965-46d4-9298-1fc66193babd",
"mime_type": "audio/mp3"
},
"type": "audio"
}

Document Message

{
"document": {
"caption": "hello world",
"filename": "the-filename.pdf",
"link": "https://whatsapp.turn.io/uploads/2024/09/24/27086/056dca1b-b8a4-49a5-8491-9561b0efe19b",
"mime_type": "application/pdf"
},
"type": "document"
}

Image Message

{
"image": {
"caption": "hello world",
"link": "https://whatsapp.turn.io/uploads/2024/09/24/27164/2bf705ba-607c-49e4-8b06-7aa1340170b8",
"mime_type": "image/jpeg"
},
"type": "image"
}

Sending outbound message status to your Channel

When you send a message to a user, you can send the status when a message is sent or delivered to a user or the user reads the delivered message sent by your Channel.

To do this you need to send a JSON object:

  • status: OBJECT, required - Status object for a message that was sent by the Channel.

Parameters for the status object

NameRequiredDescription
idyesString. The ID for the message that was sent by the Channel.
statusyesString. The status of the message that was sent by the Channel. Possible values can be one of the following: sent, delivered, read.
timestampyesString. Unix timestamp indicating when the message was sent, delivered, or read by the user.
$ curl https://whatsapp.turn.io/v1/numbers/eb2828fa-7605-4c97-bba1-ea74e25c7452/statuses \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"status": {
"id": "the-message-id",
"status": "sent",
"timestamp": "1727291610"
}
}'
> {"success": true}

Error Response

{
"message": "Bad Request",
"errors": {
"status": {
"id": [
"is invalid"
],
"status": [
"is invalid"
],
"recipient_id": [
"is invalid"
]
}
}
}