shell

Introduction

Welcome to the Turn API. Turn v1 provides access to the WhatsApp Business API. The Turn API enables business messaging for the WhatsApp Enterprise Solution.

Authentication

To get an access token, use this code:

# Use Basic Auth over HTTPS to get an access token
$ curl -X POST \
  --user 'username:password' \
  https://whatsapp.turn.io/v1/users/login


> {
   "users": [{
      "token": "eyJhbGciOHlXVCJ9.eyJ1c2VyIjoNTIzMDE2Nn0.mEoF0COaO00Z1cANo",
      "expires_after": "2018-03-01 15:29:26+00:00"
   }]
}

Make sure to replace username:password with your username and password.

You can get your username and password from the Turn UI. With it you can request an API access token.

Turn expects for the access token to be included in all API requests to the server in a header that looks like the following:

Authorization: Bearer token

Rate Limiting

Rate limits are returned via HTTP headers.

X-Ratelimit-Bucket: general
X-Ratelimit-Limit: 60
X-Ratelimit-Remaining: 21
X-Ratelimit-Reset: 1533715145

Currently all API endpoints are subject to rate limiting. We're allowing 60 calls every 60 seconds by default. The rates are limited within the scope of the number and the message type.

The buckets that apply are:

If you exceed your rate limit allowance the HTTP API will start returning an HTTP 429 error status.

If you feel the current limits need changing for your use case, please get in touch with us.

The HTTP response from the API has the following headers set which help you monitor your usage and remaining allowance.

X-Ratelimit-Bucket

How your API call was categorized. The allowance applies per categories. The default bucket is general. API calls for sending text messages are categorised as text and API calls for sending messages with any kind of media attachment are categorised as media.

X-Ratelimit-Limit

How many calls you are allowed to make to this API endpoint on behalf of the number. Currently defaults to 60.

X-Ratelimit-Remaining

How many calls are remaining within the specified time. Currently the rate limits are reset every 60 seconds.

X-Ratelimit-Reset

The Unix timestamp in Epoch seconds when the rate limit will be reset.

These rate limits are enforced per number, not per account.

Throttling

We have the ability to force API clients to slow down if they are not respecting the rate limit allowances configured.

If you see an HTTP response header with X-throttling: 1 then you are being throttled. Turn will keep the HTTP connection open until you are back within the rate limit allowance configured for your number.

Contacts

WhatsApp status check

$ curl -X POST https://whatsapp.turn.io/v1/contacts \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "blocking": "wait",
        "contacts": [
            "+16315551003",
            "+1-631-555-1002",
            "+54 9 11 5612-1008",
            "+1 (516) 283-7151"
        ]
    }
    '
> {
   "contacts": [
      {
         "input": "+1-631-555-1002",
         "status": "invalid"
      },
      {
         "input": "+16315551003",
         "status": "valid"
         "wa_id": "16315551003"
      },
      {
         "input": "+54 9 11 5612-1008",
         "status": "invalid"
      },
      {
         "input": "+1 (516) 283-7151",
         "status": "valid"
         "wa_id": "15162837151"
      }
   ]
}

Use the contacts endpoint for the following purposes:

  1. To verify that a phone number in your database belongs to a valid WhatsApp account. You must ensure that status is valid before you can message a user.

  2. To get the WhatsApp ID for a phone number. WhatsApp IDs are needed to send messages and use notifications.

As a business owner, you must present an option to opt-in to WhatsApp communications to your customers. The opt-in flow is managed by your business. After a customer opts-in, use the contacts endpoint to validate the registered number.

  1. Send a request containing an array of registered phone numbers to /v1/contacts. You will receive a response containing user statuses (status) and their WhatsApp IDs (wa_id).
  2. Save the WhatsApp IDs for those numbers that returned a status of valid. Valid users are those with a WhatsApp account. Use the WhatsApp IDs to send messages and notifications.

  3. Repeat these steps on a regular basis to manage your list of valid users. The results are cached in the WhatsApp Business API client's database for 7 days.

Best practice is to check contacts before each message send. There is no additional cost as results are cached.

Parameters

Name Required Description
blocking no Options: no_wait (default) or wait. For more information, read the Blocking section.
contacts yes Array of contact phone numbers. The numbers can be in any standard telephone number format. The recommended format for contact phone numbers is starting with a plus sign (+) and the country code.

Blocking

There are two options for the blocking parameter: no_wait and wait. By default, if the blocking parameter is not specified in a call, blocking is no_wait.

Blocking determines whether the request should wait for the processing to complete (synchronous) or not (asynchronous).

Responses

This is with blocking set to no_wait:

{
  "contacts": [
    {
      "input": "1-631-555-1002",
      "status": "processing"
    },
    {
      "input": "6315551003",
      "status": "processing"
    },
    {
      "input": "+54 9 11 5612-1008",
      "status": "processing"
    },
    {
      "input": "+1 (516) 283-7151",
      "status": "valid",
      "wa_id": "15162837151"
    }
  ]
}

After you send the request to check contacts you will receive a response with the current status of the requested numbers. Contacts that are new will typically have a status of processing as the application asynchronously determines if the numbers belong to a valid WhatsApp account.

If you use the "blocking": "wait" option in the request, the response is now synchronous, so the response is generated only once the status of all of the numbers has been determined. This implies that the request will take a longer time to return if there are new contacts, but you will not see the "processing" value returned. The example code below demonstrates this behavior.

This is with blocking set to wait:

{
   "contacts": [
      {
         "input": "1-631-555-1002",
         "status": "invalid"
      },
      {
         "input": "6315551003",
         "status": "valid"
         "wa_id": "16315551003"
      },
      {
         "input": "+54 9 11 5612-1008",
         "status": "invalid"
      },
      {
         "input": "+1 (516) 283-7151",
         "status": "valid",
         "wa_id": "15162837151"
      }
   ]
}

Parameters

The contacts response payload contains the same array of phone numbers sent in the request with the input, status, and wa_id properties.

Name Description
input The value you sent in the contacts field of the JSON request.
status Status of the user. Options include: processing (Input is still being processed), valid (Input determined to be a valid WhatsApp user),

invalid (Input determined to not be a valid WhatsApp user or the phone number is in a bad format). wa_id | WhatsApp user identifier that can be used in other API calls. Only returned if the status is valid.

Turn Contact Profile Api

By default the following fields are reserved, and immediately available, to all contacts in Turn:

Any fields created using the /v1/contacts/schema endpoint are created in addition to these fields.

To create additional fields, issue an HTTP POST request to /v1/contacts/schemas. This will create a new schema definition with a new uuid.

From that point onwards, Turn will still return contacts according to previous schemas but will only allow writing of contacts according to the most recently created schema.

$ curl -X POST https://whatsapp.turn.io/v1/contacts/schemas \
    -H 'Authorization: Bearer token' \
    -H 'Accept: application/vnd.v1+json' \
    -H 'Content-Type: application/json' \
    -d '{
        "version": "0.0.1-alpha",
        "fields": [{
            "name": "consent",
            "display": "Consent Given",
            "type": "BOOLEAN",
            "default": false
        }, {
            "name": "gender",
            "display": "Gender",
            "type": "ENUM",
            "null": false,
            "default": "UNDISCLOSED",
            "enum": [
                {"value": "MALE", "display": "Male"},
                {"value": "FEMALE", "display": "Female"},
                {"value": "OTHER", "display": "Other"},
                {"value": "UNDISCLOSED", "display": "Undisclosed"}
            ]
        }]
    }'
> {
    "version": "0.0.1-alpha",
    "uuid": "<the-new-schema-uuid>",
    "fields": [{
        "name": "consent",
        "display": "Consent Given",
        "type": "BOOLEAN",
        "default": false
    }, {
        "name": "gender",
        "display": "Gender",
        "type": "ENUM",
        "null": false,
        "default": "UNDISCLOSED",
        "enum": [
            {"value": "MALE", "display": "Male"},
            {"value": "FEMALE", "display": "Female"},
            {"value": "OTHER", "display": "Other"},
            {"value": "UNDISCLOSED", "display": "Undisclosed"}
        ]
    }]
}

The response is the schema with the uuid value identifying the schema in the payload.

The support field types are:

All fields can be configured to accept null values by specifying "null": true or "null": false.

The only exception is the BOOLEAN field type.

The BOOLEAN field requires a default value set to either true or false, it cannot be configured to allow null values.

All fields must supply a default value, this can be null if "null": true is specified, with the exception of BOOLEAN fields.

The display parameter is the human friendly display name for the field.

The name parameter must be lower case, must start with a letter, and can only contain lower case alphanumeric characters and underscores.

The enum parameter only accepts a list of options. Options are objects have a value and a display field. The value must be upper case, must start with a letter, and can only contain upper case alphanumeric characters or underscores.

To retrieve an older schema do an HTTP GET request to the /v1/contacts/schemas/<uuid> endpoint.

Retrieving a Contact Profile

To retrieve a contact's profile details make a call to the /v1/contacts/<wa-id>/profile endpoint

$ curl https://whatsapp.turn.io/v1/contacts/27123456789/profile \
    -H 'Authorization: Bearer token' \
    -H 'Accept: application/vnd.v1+json'
> '{
    "version": "0.0.1-alpha",
    "schema": "<the-schema-uuid>",
    "generation": 0,
    "fields": {
        "language": "ENG",
        "date_of_birth": null,
        "age": 2,
        "consent": false,
        "location": null,
        "name": null,
        "risk_profile": 2.0,
        "surname": null,
        "birthday": null,
        "opted_in": false,
        "opted_in_at": null,
        "whatsapp_id": null,
        "whatsapp_profile_name": null
    }
}'

To retrieve a contact profile for an earlier schema, supply the schema uuid as a query parameter:

$ curl "https://whatsapp.turn.io/v1/contacts/27123456789/profile?schema=<the-schema-uuid>" \
    -H 'Authorization: Bearer token' \
    -H 'Accept: application/vnd.v1+json'
> '{
    "version": "0.0.1-alpha",
    "schema": "<the-schema-uuid>",
    "generation": 0,
    "fields": {
        "language": "ENG",
        "date_of_birth": null,
        "age": 2,
        "consent": false,
        "location": null,
        "name": null,
        "risk_profile": 2.0,
        "surname": null,
        "birthday": null,
        "opted_in": false,
        "opted_in_at": null,
        "whatsapp_id": null,
        "whatsapp_profile_name": null
    }
}'

The generation field automatically increments by 1 for every change made to the contact's profile.

To replace a Contact Profile

To replace a contact's profile full details make an HTTP PUT call to the /v1/contacts/<wa-id>/profile endpoint. If the full set is not supplied, the defaults from the schema are applied for the missing fields.

$ curl -X PUT https://whatsapp.turn.io/v1/contacts/27123456789/profile \
    -H 'Authorization: Bearer token' \
    -H 'Accept: application/vnd.v1+json' \
    -H 'Content-Type: application/json' \
    -d '{"name": "Fizbo"}'
> '{
    "version": "0.0.1-alpha",
    "generation": 1,
    "schema": "<the-schema-uuid>",
    "fields": {
        "language": nil,
        "date_of_birth": null,
        "age": 0,
        "consent": false,
        "location": null,
        "name": "Fizbo",
        "risk_profile": 0.0,
        "surname": null,
        "birthday": null,
        "opted_in": false,
        "opted_in_at": null,
        "whatsapp_id": null,
        "whatsapp_profile_name": null
    }
}'

To partially update a Contact profile

To partially update a contact's profile make an HTTP PATCH call to the /v1/contacts/<wa-id>/profile endpoint and only supply the fields needing to be updated.

The details from the previous contact profile generation are automatically inherited.

$ curl -X PATCH https://whatsapp.turn.io/v1/contacts/27123456789/profile \
    -H 'Authorization: Bearer token' \
    -H 'Accept: application/vnd.v1+json' \
    -H 'Content-Type: application/json' \
    -d '{"name": "Fizbo"}'
> '{
    "version": "0.0.1-alpha",
    "generation": 2,
    "schema": "<the-schema-uuid>",
    "fields": {
        "language": "ENG",
        "date_of_birth": null,
        "age": 2,
        "consent": false,
        "location": null,
        "name": "Fizbo",
        "risk_profile": 2.0,
        "surname": null,
        "birthday": null,
        "opted_in": false,
        "opted_in_at": null,
        "whatsapp_id": null,
        "whatsapp_profile_name": null
    }
}'

Retrieving Contact Profiles

It is possible to download all the contact profiles for a number in either CSV or in JSONL format.

$ curl -X GET https://whatsapp.turn.io/v1/export/contacts/details \
    -H 'Authorization: Bearer token'
> 'language,date_of_birth,age,consent,location,name,risk_profile,surname,birthday
ENG,,2,false,,Fizbo,2.0,,
'

To download the contact profiles as JSON, specify the ?format=json as a query parameter:

$ curl -X GET https://whatsapp.turn.io/v1/export/contacts/details?format=json \
    -H 'Authorization: Bearer token'
> '{"language":"ENG","date_of_birth":null, ...}
{"language":"AFR","date_of_birth":null, ...}'

In the JSONL format the contact details are returned as a single JSON object per line as per the JSONL specification.

Contact Fallback Parameters

The Turn contacts API allows you to view or edit a contact's fallback parameters.

View a contact's contact fallback parameters:

$ curl -X GET https://whatsapp.turn.io/v1/contacts/16315551003 \
    -H 'Authorization: Bearer token' \
    -H "Accept: application/vnd.v1+json" \
    -H 'Content-Type: application/json'
> {
   "failure_count": 3,
   "id": "contact-uuid",
   "is_fallback_active": true,
   "type": "DEFAULT"
}

To update a contact's fallback parameters:

$ curl -X PATCH https://whatsapp.turn.io/v1/contacts/16315551003 \
    -H 'Authorization: Bearer token' \
    -H "Accept: application/vnd.v1+json" \
    -H 'Content-Type: application/json' \
    -d '
    {
        "is_fallback_active": false
    }
    '
> {
   "failure_count": 3,
   "id": "contact-uuid",
   "is_fallback_active": false,
   "type": "DEFAULT"
}

Messages

https://whatsapp.turn.io/v1/messages

This endpoint responsible for sending messages via WhatsApp. Messages for WhatsApp are classified as:

Turn's outbound messaging API is synchronous to the upstream gateways. If Turn returns with a success response, the upstream gateway has accepted the message for delivery. Turn does not queue outbound messaging.

Text Messages

$ curl -X POST "https://whatsapp.turn.io/v1/messages" \
  -H "Authorization: Bearer token" \
  -H "Content-Type: application/json" \
  -d '
    {
        "preview_url": false | true,
        "recipient_type": "individual",
        "to": "whatsapp-id",
        "type": "text",
        "text": {
            "body": "your-text-message-content"
        }
    }'


> {
  "messages": [{
    "id": "gBEGkYiEB1VXAglK1ZEqA1YKPrU"
  }]
}

Make sure to replace token with your access token.

A successful response includes a messages object with an identifier for the newly created message.

An unsuccessful response will contain an error message. See Error and Status Codes for more information.

Parameters

Parameter Required Default Description
preview_url no false Specifying preview_url in the request is optional when not including a URL in your message. To include a URL preview, set preview_url to true in the message body and make sure the URL begins with http:// or https://.
to yes When recipient_type is individual, this field is the WhatsApp ID (phone number) returned from contacts endpoint.
type no text Specifying type in the request is optional when you are sending a text message.
text.body yes The text to send

Media Messages

Use the messages endpoint to send messages containing audio, images, documents, or stickers to your customers.

When you send a message that includes media, you must provide the ID of the uploaded media in the request body. You must also specify the type of media that you are sending: audio, image, video, document, or sticker. When the request is received, the media is uploaded to the WhatsApp server and sent to the user indicated in the to field.

$ curl -X POST https://whatsapp.turn.io/v1/messages \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "recipient_type": "individual",
        "to": "whatsapp-id",
        "type": "audio" | "document" | "image" | "sticker" | "video",

        "audio": {
            "id": "your-media-id",
        }

        "document": {
            "id": "your-media-id",
            "caption": "your-document-caption"
        }

        "image": {
            "id": "your-media-id",
            "caption": "your-image-caption"
        }

        "sticker": {
            "id": "your-media-id"
        }

        "video": {
            "id": "your-media-id",
        }
    }
    '

> {
    "messages": [{
        "id": "gBEGkYiEB1VXAglK1ZEqA1YKPrU"
    }]
}

The successful response includes a messages object with a message ID. An unsuccessful response will contain an error message. See Error and Status Codes for more information on errors.

Parameters

Name Required Default Description
to yes This field is the WhatsApp ID (phone number) returned from contacts endpoint.
type yes no text
<media-type>.id The media object ID, which is returned when the media is successfully uploaded to the WhatsApp Business API Client with the media endpoint.
<media-type>.caption Describes the specified image or document media. Do not use with audio or sticker media.

Contact Checks

As a convenience, Turn.io's APIs allows one to combine the contact check on /v1/contacts and the message sending call in a single combined API call to /v1/messages by setting the X-Turn-Contact-Check HTTP header.

$ curl -X POST https://whatsapp.turn.io/v1/messages \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -H 'X-Turn-Contact-Check: 1' \
    -d '{ ... }'

This will result in Turn first calling the /v1/contacts API internally with blocking set to true, only then will it proceed to call the /v1/messages API.

The result of this API check is returned in the X-Turn-Contact-Check-Status HTTP header in the response. The value of this will be either valid, invalid, failed, or pending as per the Contacts check documentation.

If the status is invalid, Turn will automatically enable the fallback channel for the recipient and the message will be delegated to the fallback channel for delivery

Additional documentation:

Templated Messages

$ curl -X POST https://whatsapp.turn.io/v1/messages \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "to": "whatsapp_id",
        "type": "template",
        "template": {
            "namespace": "template-namespace",
            "name": "template-name",
            "language": {
                "code": "en_us",
                "policy": "deterministic"
            },
            "components": [
                {
                    "type" : "header",
                    "parameters": [
                        {
                            "type": "text",
                            "text": "header placeholder value"
                        }
                    ]
                },
                {
                    "type": "body",
                    "parameters": [
                        {
                            "type": "text",
                            "text": "body placeholder 1 value"
                        },
                        {
                            "type": "text",
                            "text": "body placeholder 2 value"
                        }
                    ]
                }
            ]
        }
    }'

> {
  "messages": [{
    "id": "gBEGkYiEB1VXAglK1ZEqA1YKPrU"
  }]
}

As with normal messages, the message is sent to the /v1/messages endpoint. What's different about sending templated messages is that the message type is set to template and a template object is provided with information such as which message template to use.

The template property indicates the namespace and the name of the message template that should be sent. It also includes a list of components that will be used to build the template header, footer, body and buttons.

Please refer to the official WhatsApp documentation for more details.

Interactive Templated Messages

$ curl -X POST https://whatsapp.turn.io/v1/messages \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "to": "whatsapp_id",
        "type": "template",
        "template": {
            "namespace": "template-namespace",
            "name": "template-name",
            "language": {
                "code": "en_us",
                "policy": "deterministic"
            },
            "components": [
                {
                    "type": "body",
                    "parameters": [
                        {
                            "type": "text",
                            "text": "body placeholder 1 value"
                        }
                    ]
                },
                {
                    "type": "button",
                    "sub_type" : "quick_reply",
                    "index": "0",
                    "parameters": [
                        {
                            "type": "payload",
                            "payload":"button-0-payload"
                        }
                    ]
                },
                {
                    "type": "button",
                    "sub_type" : "quick_reply",
                    "index": "1",
                    "parameters": [
                        {
                            "type": "payload",
                            "payload":"button-1-payload"
                        }
                    ]
                }
            ]
        }
    }'

> {
  "messages": [{
    "id": "gBEGkYiEB1VXAglK1ZEqA1YKPrU"
  }]
}

Sending an interactive message simply boils down to sending a templated message using a template that contains buttons.

For quick reply buttons, it is possible to provide a custom payload for each button. The payload will be part of the incoming message notification that will be sent by WhatsApp whenever the button is clicked.

Please refer to the official WhatsApp documentation for more details.

Media Templated Messages

$ curl -X POST https://whatsapp.turn.io/v1/messages \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "to": "whatsapp_id",
        "type": "template",
        "template": {
            "namespace": "template-namespace",
            "name": "template-name",
            "language": {
                "code": "en_us",
                "policy": "deterministic"
            },
            "components": [
                {
                    "type": "header",
                    "parameters": [
                        {
                            "type": "video",
                            "video": {
                                "link": "https://url.com/video-file.mp4"
                            }
                        }
                    ]
                }
            ]
        }
    }'

> {
  "messages": [{
    "id": "gBEGkYiEB1VXAglK1ZEqA1YKPrU"
  }]
}

Sending a media templated message simply means sending a templated message using a template that that has a media header. When sending the templated message, the components array of the request object should provide the URL of the media file you intend to send inside its parameters object.

Please refer to the official WhatsApp documentation for more details.

Formatting in Text Messages

WhatsApp allows some formatting in messages. To format all or part of a message, use these formatting symbols:

Formatting Symbol Example
bold Asterisk(*) Your total is *\$10.50*.
italics Underscore(_) Welcome to WhatsApp!
strikethrough Tilde(~) This is ~better~ best!
code Three backticks (```) ```print 'Hello World';```

HSM (Deprecated)

$ curl -X POST https://whatsapp.turn.io/v1/messages \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "to": "whatsapp_id",
        "type": "hsm",

        "hsm": {
            "namespace": "8bea15c0_b636_0737_7912_63f2b9a6bd09"
            "element_name": "purchase_with_credit_card",
            "language": {
                "policy": "fallback" | "deterministic",
                "code": "en_US"
            },
            "localizable_params": [
                { "default": "$10" },
                { "default": "1234" }
            ]
        }
    }'

> {
  "messages": [{
    "id": "gBEGkYiEB1VXAglK1ZEqA1YKPrU"
  }]
}

As with other messages, the message is sent to the /v1/messages endpoint. What's different about sending Message Templates is the content of the message body specified with the hsm parameter.

The hsm parameter contains a namespace and an element_name pair that identify a template and the values to apply to variables in that template.

Additional documentation for:

Localizable Parameters for Templated Messages

$ curl -X POST https://whatsapp.turn.io/v1/messages \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "to": "whatsapp_id",
        "type": "hsm",

        "hsm": {
            "namespace": "8bea15c0_b636_0737_7912_63f2b9a6bd09"
            "element_name": "purchase_with_credit_card",
            "language": {
                "policy": "fallback" | "deterministic",
                "code": "en_US"
            }
            "localizable_params": [
                { "default": "$10" },
                { "default": "1234" }
            ],
        }
    }
    '

The following code provides an example of sending a Message Template. This example uses the purchase_with_credit_card element name within the 8bea15c0_b636_0737_7912_63f2b9a6bd09 namespace.

The resulting message received by the customer will look like this:

You made a purchase for $10 using a credit card ending in 1234.

As you can see from looking at the localizable parameters and the message the client received, the template used the default value of 10 as the amount of the purchase and the default value of 1234 as the last numbers of the account.

Parameters

Name Required Description
type yes Must be hsm for templated messages. HSM stands for Highly Structured Messages
to yes The WhatsApp ID to whom the message is being sent
hsm yes The containing element for the message content — Indicates that the message is highly structured. Parameters contained within provide the structure. See Parameters for the HSM object for more details.

Parameters for the HSM object

Name Required Description
namespace yes The namespace that will be used
element_name yes The element name that indicates which template to use within the namespace
language yes Allows for the specification of a deterministic or fallback language. See the Parameters for the Language object section for more information.

Parameters for the Language object

The language parameter sets the language policy for an Message Template; you can set it to either fallback or deterministic.

Name Required Description
policy yes The language policy the message should follow. Possible values: fallback or deterministic
code yes The code of the language or locale to use — Accepts both language and language_locale formats (e.g., en and en_US).

When sending a Message Template, the hsm object is required. To define Message Templates, you specify a namespace and an element_name pair that identify a template. Templates have parameters that will be dynamically incorporated into the message. For the example used in this document, the Message Template looks like this:

"You made a purchase for {{1}} using a credit card ending in {{2}}."

For "namespace": "8bea15c0_b636_0737_7912_63f2b9a6bd09" with "element_name": "purchase_with_credit_card", the first value you list replaces the {{1}}variable in the template message and the second value you list replaces the {{2}} variable.

Some of these parameters (e.g., date-time or currency) are localizable so that they are displayed appropriately based on the customer's language and locale preferences. If the device is unsuccessful in localizing a parameter, it will fallback to the value that is provided as the "default" value.

The localizable_params options are shown in the table below. For more information on localizable parameters, read the Localization section.

Name Type Required Description
default String yes Default text if localization fails
currency currency object no If the currency object is used, it contains required parameters currency_code and amount_1000.
date_time date_time object no If the date_time object is used, further definition of the date and time is required. See the example below for two of the options.

All of the localization parameters must have a default value. The default value is all that is needed when you are specifying text.

However, to specify currency and date in addition to the default, when applicable, use the currency and date_time objects, as shown below. This will allow the client to attempt to localize this data the best way possible and fallback to the default option only if they cannot localize the data. Note that using the currency and date_time objects for appropriate parameters is highly recommended to enable an optimal localized experience for your customers.

Example Template Localization

The following is a complete example that demonstrates the use of all of the currently supported localized types:

$ curl -X POST https://whatsapp.turn.io/v1/messages \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "to": "19195551112",
        "type": "hsm",
        "recipient_type": "individual",

        "hsm": {
            "namespace": "8bea15c0_b636_0737_7912_63f2b9a6bd09",
            "element_name": "four_lparam_demo",
            "language": {
                "policy": "deterministic",
                "code": "en_US"
            }

            "localizable_params": [
                {
                    "default": "just a string"
                },
                {
                    "default": "$100.99",
                    "currency": {
                        "currency_code": "USD",
                        "amount_1000": 100990
                    }
                },
                {
                    "default": "February 25, 1977",
                    "date_time": {
                        "component": {
                            "day_of_week": 5,
                            "day_of_month": 25,
                            "year": 1977,
                            "month": 2,
                            "hour": 15,
                            "minute": 33
                        }
                    }
                },
                {
                    "default": "January 26, 2017",
                    "date_time": {
                        "unix_epoch": {
                            "timestamp": 1485470276
                        }
                    }
                }
            ]
        }
    }
    '

Media

To use media in messages, the media file must be uploaded to the media endpoint. Once the upload is complete, you can send a message by referring to the media ID.

When a media message is sent, the media is stored on the WhatsApp servers for 7 days. If a user makes a request to download the media after 7 days, the WhatsApp servers will request the same media file from the WhatsApp Business API client. If the media has been removed, the user will be notified that the media is unavailable. It is not safe to assume the media was downloaded simply based on the delivered and read receipts. Outgoing media is generally safe to be removed past 30 days, but you should employ a strategy that best suits your business.

Uploading Media

$ curl -X POST https://whatsapp.turn.io/v1/media
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: image/jpeg' \
    --data-binary @your-file-path

> {
    "media": [
        {
            "id": "f043afd0-f0ae-4b9c-ab3d-696fb4c8cd68"
        }
    ]
}

To upload media to the WhatsApp Business API client, use the media endpoint. The POST body must contain the binary media data and the Content-Type header must be set to the type of the media being uploaded. See the Supported File Types section for information about supported file types.

A successful response returns the id field, which is the information you need for retrieving messages and sending a media message to your customers.

Supported File Types

Media Supported File Types
audio AAC, M4A, AMR, MP3, OGG, OPUS
document PDF, DOC(X), PPT(X), XLS(X)
image JPG, JPEG, PNG
sticker WEBP
video MP4, 3GPP

Post-Processing Media Size

The following are the post-processing size limits imposed by WhatsApp on the various media types. WhatsApp won't deliver messages with media files with a post-processing size larger than the limits outlined below.

Media Type Size
audio 16 MB
document 100 MB
image 5 MB
sticker 100 KB
video 16 MB

Stickers

Stickers must have a transparent background, be exactly 512 x 512 pixels, and be less than 100 KB in size. As indicated in the Supported File Types table, only webp format files are accepted. Other file types can be transformed into webp online by using a file conversion site such as cloudconvert.

Retrieving Media

$ curl -X GET https://whatsapp.turn.io/v1/media/the-media-id

After you have successfully completed Uploading Media, you will receive a response containing a media ID. You will use that ID in the request to retrieve the media stored in the WhatsApp Business API client.

Retrieving media is particularly useful when a user has uploaded an image that is sent to your Webhook. When a message with media is received, the WhatsApp Business API client will download the media. Once the media is downloaded, you will receive a notification through your webhook. Use the media ID found in that notification to retrieve the media.

Deleting Media

To delete media in the WhatsApp Business API client, you will send a DELETE request to the media node along with the ID of the media that you want to delete. You will use the ID from the response to the Uploading Media or Media Message from a Webhook request.

$ curl -X DELETE https://whatsapp.turn.io/v1/media/the-media-id

Health

$ curl -X GET https://whatsapp.turn.io/v1/health \
    -H 'Authorization: Bearer token'

> {
    "health": {
       "gateway_status": "connected | connecting | uninitialized | unregistered"
    }
}

Check the status of your WhatsApp Business API Client with the health endpoint. This endpoint samples the health of the gateway and sends back a JSON response.

Webhooks

Webhooks are configurable via the Turn UI.

Webhook Subscriptions

Each Webhook must specify at least 1 subscription. A subscription is an identifier for what type of events a webhook endpoint should be receiving.

Currently the following two subscription types are supported: whatsapp and turn.

The subscription type is submitted along with the HTTP request as an HTTP Header.

X-Turn-Hook-Subscription: whatsapp

WhatsApp Subscriptions

This is the default subscription type. All events generated by the WhatsApp Enterprise Client are dispatched to any webhook subscribed to this subscription type. If you are interested in receiving inbound messages or message delivery notifications from your target audience, this is the subscription you should be using. The details of these events are documented below in Inbound Message Webhooks and Outbound Status Notifications.

Turn Subscriptions

X-WhatsApp-Id: <whatsapp's message id>

This is an subscription type that is generated for messages created through the messaging API that Turn exposes. If you are interested in receiving webhook events for whenever an API client dispatches an outbound message to someone in your target audience on WhatsApp, this is the subscription you should be using.

The webhooks receive the exact same payload as was submitted to the HTTP API endpoint.

An extra HTTP Header `X-WhatsApp-Idis provided along with the original payload. The value of the header is theId that WhatsApp has assigned to this message and which any message delivery status updates will refer to.

Inbound Message Webhooks

{
    "contacts": [{
        "profile": {
            "name": "Kerry Fisher"
        },
        "wa_id": "16315551234"
    }],
    "messages": [{
        "context": {
            "from": "sender_wa_id_of_context_message",
            "id": "message_id_of_context_message"
        },

        "from": "sender_wa_id",
        "id": "message_id",
        "timestamp": "message_timestamp",
        "type": "audio | document | image | location | system | text | video | voice | contacts",

        # Only in case of error, errors field (array) will be present
        "errors": [ { ... } ],

        "audio": {
            "file": "absolute_filepath_on_coreapp",
            "id": "media id",
            "link": "link to audio file",
            "mime_type": "media mime type",
            "sha256": "checksum"
        },

        "document": {
            "file": "absolute_filepath_on_coreapp",
            "id": "media id",
            "link": "link to document file",
            "mime_type": "media mime type",
            "sha256": "checksum",
            "caption": "document caption"
        },

        "image": {
            "file": "absolute_filepath_on_coreapp",
            "id": "media id",
            "link": "link to image file",
            "mime_type": "media mime type",
            "sha256": "checksum",
            "caption": "image caption"
        },

        "location": {
            "address": "1 hacker way, menlo park, ca, 94025",
            "latitude": latitude,
            "longitude": longitude,
            "name": "location name"
        },

        "system": {
            "body": "system message content"
        },

        "text": {
            "body": "text message content"
        },

        "video": {
            "file": "absolute_filepath_on_coreapp",
            "id": "media id",
            "link": "link to video file",
            "mime_type": "media mime type",
            "sha256": "checksum"
        },

        "voice": {
            "file": "absolute_filepath_on_coreapp",
            "id": "media id",
            "link": "link to audio file",
            "mime_type": "media mime type",
            "sha256": "checksum"
        }

        "contacts": [{
            "addresses": [{
                "city": "Menlo Park",
                "country": "United States",
                "country_code": "us",
                "state": "CA",
                "street": "1 Hacker Way",
                "type": "WORK",
                "zip": "94025"
            }],
            "birthday": "2012-08-18",
            "contact_image": "/9j/4AAQSkZJRgABAQEAZABkAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABgAGADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD1NLloxyc0j3bNUHDGgoCMVNkBbhutvVqc9yGHNZ3kOW4Wn/ZnQZzSsgLLXAA61Xn1O2t4zLcyJHGvV3OAKMADmvOviLq9m11a2bO/l2xLzoqnliF2j9f1NTLRXHGHM7Hb2vivSL+8e0s7yKaVeyHg/Q9D+FXWuHPOzivFodT0WZkeF44bheVdE2EH+Rr2+zuVuNPt7gqh86JX+TkcjPFTCdy6lNQ2dyr5+G5WpRe4HTFWWSOQfwioZbCIn5XNaXRmQPegjmqrKLh+...",
            "emails": [{
                "email": "kfish@fb.com",
                "type": "WORK"
            }],
            "ims": [{
                "service": "AIM",
                "user_id": "kfish"
            }],
            "name": {
                "first_name": "Kerry",
                "formatted_name": "Kerry Fisher",
                "last_name": "Fisher"
            },
            "org": {
                "company": "Facebook"
            },
            "phones": [{
                "phone": "+1 (940) 555-1234",
                "type": "CELL"
            }, {
                "phone": "+1 (650) 555-1234",
                "type": "WORK",
                "wa_id": "16505551234"
            }],
            "urls": [{
                "url": "https://www.facebook.com",
                "type": "WORK"
            }]
        }]
    }]
}

The general format of the notifications Webhook is shown. You will need create a endpoint handler that is triggered by the sending or receiving of a message. The following sections provide more detail for each type of notification.

Marking Inbound messages as read

You can use the /v1/messages/<message-id> endpoint to change the status of incoming messages to read.

$ curl -X PUT "https://whatsapp.turn.io/v1/messages/<message-id>" \
  -H "Authorization: Bearer token" \
  -d '
    {
        "status": "read"
    }'
> {}

Make sure to replace token with your access token.

Outbound Status Notifications

Message was sent

{
  "statuses": [
    {
      "id": "ABGGFlA5FpafAgo6tHcNmNjXmuSf",
      "recipient_id": "16315555555",
      "status": "sent",
      "timestamp": "1518694700"
    }
  ]
}

Message was read

{
  "statuses": [
    {
      "id": "ABGGFlA5FpafAgo6tHcNmNjXmuSf",
      "recipient_id": "16315555555",
      "status": "read",
      "timestamp": "1518694700"
    }
  ]
}

The WhatsApp Business API client will send notifications to inform you of the status of messages you send to users. When the message is sent successfully, you will receive a notification when the message is sent, delivered, and read. The order of these notifications in your app may not reflect the actual timing of the message status. View the timestamp to determine the timing, if necessary. The notifications you may receive are:

  1. sent - Message was received by the server. To receive notifications for sent messages, set the sent_status setting to true in the application settings. Sent status notification is off by default.
  2. delivered - Message was delivered to user's device.
  3. read - Message was read by the user. Read notifications will only be available for those users that have read receipts enabled. For users that do not have it enabled, you will only receive the delivered notification.
  4. failed - Message failed to send.

Security

See an example below of how the signature can be calculated using the Python programming language.

>>> import hmac
>>> import base64
>>> from hashlib import sha256
>>> h = hmac.new("secret", '{"foo":"bar"}', sha256)
>>> base64.b64encode(h.digest())
'PzqzmGtlarsXrz6xRD7WwI74//n+qDkVkJ0bQhrsib4='

The signature is sent as an HTTP header to the webhook endpoints you have configured.

X-Turn-Hook-Subscription: whatsapp
X-Turn-Hook-Signature: PzqzmGtlarsXrz6xRD7WwI74//n+qDkVkJ0bQhrsib4=

Each webhook has an hmac_digest and hmac_secret set. These are used to calculated a signature for the webhook payload. Using this signature you can verify that the webhook was indeed received from Turn.

The signature is sent as an HTTP header in the webhook posted to the endpoints you have configured.

Notification Errors

When there is an error in the sending or receiving of a notification, the errors array will provide a description of the error. This type of error can be caused by transient network connectivity errors, invalid credentials, management controllers in unavailable status, etc.

{
    "errors": [{
       "code": <error-code>,
       "title": "<error-title>",
       "details": "<error-description>",
       "href": "location for error detail"
    },
    {
       ...
    }
    ]
}

Settings

Turn only exposes a subset of the WhatsApp Business API. Each section and the functionality exposed is listed below.

Application Settings

Setting the application settings

$ curl -X PATCH https://whatsapp.turn.io/v1/settings/application \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "webhooks": {
            "url": "https://example.com"
        }
    }
    '

> {}

Retrieving the application settings

$ curl -X GET https://whatsapp.turn.io/v1/settings/application \
    -H 'Authorization: Bearer token'

> {
    "settings": {
        "application": {
            "webhooks": {
                "url": "https://example.com"
            }
        }
    }
}

Configure your primary webhook with this endpoint. The primary webhook is given priority when relaying messages to your endpoints.

Before an event is dispatched to your configured webhook endpoint, it is queued in Turn. Under normal conditions this is a matter of milliseconds.

Turn operates 3 levels of queue priority, low, default, and high. The low priority queue is drained with least urgency and the high priority queue is given maximum amount of resources to ensure fast delivery.

Which queues your events are dispatched to depends on how fast your endpoint responds. The response time of your endpoints are sampled once every 20 webhook event deliveries. The time it takes you to accept a message for delivery determines what queue the following event is going to be dispatched to.

Queue Millisecond threshold
low 1 second or more
default Longer than 200ms but less than 1 second
high 200ms or less

Turn's commitment is that if your webhook endpoint is fast in processing messages we will commit to process your messages as fast as we can. If your webhook endpoint is slow, we will treat the events with low priority.

Sampling of your webhook response time is constantly ongoing and as you change your processing speed you will automatically be moved to a higher or lower queue priority.

$ curl -X DELETE https://whatsapp.turn.io/v1/settings/application \
    -H 'Authorization: Bearer token'

Settings can be reset with an HTTP DELETE call to the endpoint. At this point that only results in clearing of the primary webhook, if one was set.

Setting the Business About Settings

Setting the business about

$ curl -X PATCH https://whatsapp.turn.io/v1/settings/profile/about \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "text": "your-profile-about-text"
    }
    '
> {}

Use the /v1/settings/profile/about endpoint to configure your about text:

Parameter

Name Type Description
text string Text to display in your profile's About section. The max length for the string is 139 characters.

A successful request returns the HTTP Status Code 200 OK and either null or {}.

Viewing the Business About Settings

Retrieving the business about

$ curl -X GET https://whatsapp.turn.io/v1/settings/profile/about \
    -H 'Authorization: Bearer token' 
> '{
  "settings": {
    "profile": {
      "about": {
        "text": "your-profile-about-text"
      }
    }
  }
}'

A successful response contains the profile object with the text parameter containing your profile's About content.

Setting the Business Profile Settings

Setting the business profile

$ curl -X POST https://whatsapp.turn.io/v1/settings/business/profile \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '
    {
        "address": "your-business-address",
        "description": "your-business-description",
        "email": "your-business-email",
        "vertical": "your-business-industry",
        "websites": [ "your-website-1", "your-website-2" ]
    }
    '

> {}

Retrieving a business profile

$ curl -X GET https://whatsapp.turn.io/v1/settings/business/profile \
    -H 'Authorization: Bearer token'

> {
   "settings": {
     "business": {
        "profile": {
          "address": "business-address",
          "description": "business-description",
          "email": "business-email",
          "vertical": "business-industry",
          "websites": [ "website-1", "website-2" ]
        }
      }
}

Use the /v1/settings/business/profile endpoint to configure your business profile settings such as:

Currently, settings can only be configured as a group. Future releases may enable setting individual settings.

Parameter

Name Type Description
address string address of the business. Maximum of 256 characters
description string Description of the business. Maximum of 256 characters
email string Email address to contact the business. Maximum of 128 characters
vertical string Industry of the business. Maximum of 128 characters
websites array of strings URLs associated with business (e.g., website, Facebook page, Instagram). Maximum of 2 websites with a maximum of 256 characters each

A successful request returns the HTTP Status Code 200 OK and either null or {}.

Setting the Profile Photo

$ curl -X POST https://whatsapp.turn.io/v1/settings/profile/photo \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: image/jpeg' \
    --data-binary @your-path-to-image

To change your profile photo using the API, send the raw image to the /v1/settings/profile/photo endpoint.

Profile photos can be of any dimension and size. The WhatsApp Business API Client will scale and crop it to be a square with a max edge of 640 and max size of 63KB before uploading to our servers. An image size of 640x640 is recommended.

A successful request returns the HTTP Status Code 200 OK and either null or {}.

Retrieve Profile Photo

$ curl -X GET https://whatsapp.turn.io/v1/settings/profile/photo \
    -H 'Authorization: Bearer token'

Use the WhatsApp Business API's /v1/settings/profile/photo endpoint to retrieve the image that you are using as your profile photo.

Two-Step Verification

$ curl -X POST https://whatsapp.turn.io/v1/settings/account/two-step \
    -H 'Authorization: Bearer token' \
    -H 'Content-Type: application/json' \
    -d '{"pin": "859274"}
$ curl -X DELETE https://whatsapp.turn.io/v1/settings/account/two-step \
    -H 'Authorization: Bearer token'

A successful request returns the HTTP Status Code 200 OK.

Use two-step verification to add an extra layer of security to the WhatsApp Business API Client. When you have two-step verification enabled, any attempt to register your phone number on WhatsApp must be accompanied by the six-digit PIN that you create using this feature. Two-step verification can be enabled and disabled using the /v1/settings/account/two-step endpoint.

To enable two-step verification, use HTTP POST with the pin parameter on the /v1/settings/account/two-step endpoint.

To remove two-step verification, use HTTP DELETE.

Errors

Application Error Codes

Error Code Title Description
1000 Generic error
1001 Message too long Message length exceeds 4096 characters
1002 Invalid recipient type Valid recipient types are: individual, group
1003 Not a group participant User is not a participant of the group
1004 Resource already exists Possible UUID conflict for media upload request or media with that UUID already exists
1005 Access denied Media directory is not writable (upload request) or Invalid credentials or VName Certificate Error or App Expired: a version upgrade is required
1006 Resource not found File or resource not found
1007 Recipient blocked to receive message Recipient is not whitelisted
1008 Required parameter is missing Missing a required parameter
1009 Parameter value is not valid Value entered for a parameter is of the wrong type or other problem
1010 Parameter is not required Contains parameter that is not required
1011 Service not ready
1012 Group is unknown You will receive this error when you send messages to a group in which you are the only member. Add some members to the group before sending messages.
1013 User is not valid
1014 Internal error Upload failed on bad image (image not uploaded correctly) or endpoint not found
1015 Too many requests
1016 System overloaded If the system is under heavy load, this error is returned to allow the system to recover from the load.
1017 Not Primary Master You receive this error when a master-only request, such as set settings, get settings, import, export, code request, register, etc. is sent to a node that is not a primary master. This could happen when the WhatsApp Business API Client is not setup properly or internal errors. Retrying the request should resolve this error most of time. If not, please contact us for support.
1018 Not Primary Coreapp You receive this error when requests are sent to a Coreapp node that is not the shard owner or in the process to become the shard owner. You might see this error when we are doing shard failover in the multiconnect setup. Retrying the request should resolve this error most of time. If not, please contact Support.
1019 Not a group admin
1020 Bad group
1021 Bad User You will receive this message when you send a message to yourself. To resolve, send the message to number that is not your own.
1022 Webhooks URL is not configured You will receive this error if you have not configured the REST API Webhooks format.
1023 Database error occurred

HTTP Status Codes

HTTP Code Description
2xx Success
200 Success (OK)
201 Successfully created (For POST requests)
4xx Client Errors
400 Request was invalid
401 Unauthorized
404 Not found
420 Message is rate limited
5xx Server Errors
500 Internal server error
504 Timeout

API Extensions

The Turn API is completely aligned with the official WhatsApp Business API. However, we do provide a few extensions that are exposed via HTTP headers.

Bidirection webhooks

We expose webhooks for outbound messaging in addition the the inbound webhooks that the WhatsApp Business API supports. The section on webhooks explains this in further detail.

Message retrieval API

$ curl -X GET \
    -H 'Accept: application/vnd.v1+json' \
    -H 'Authorization: Bearer token' \
    https://whatsapp.turn.io/v1/contacts/<wa-id>/messages
> '
{
    "chat": {
        "owner": "<wa-id>",
        "assigned_to": {
            "id": "account assignment id",
            "name": "name of account holder",
            "type": "OPERATOR"
        } | null,
        "state": "OPEN",
        "state_reason": "Re-opened by inbound message.",
        "unread_count": 1
    },
    "messages": [{
        "to": "wa-id",
        "from": "another-wa-id",
        "type": "text",
        ...

        "_vnd": {
            "v1": {
                "direction": "outbound",
                "in_reply_to": "an-earlier-inbound-external-id",
                "author": {
                    "name": "the name of the author",
                    "type": "SYSTEM | OPERATOR"
                }
            }
        }
    }]
}

Performing an HTTP GET request with our vendor specific Accept: application/vnd.v1+json header unlocks this functionality. We only return the 50 most recent messages sent and received for the given WhatsApp ID, ordered by message timestamp descending.

We return the original WhatsApp message but include our vendor specific _vnd key in the payload with supplementary information Turn has on record.

Label retrieval API

$ curl -X GET \
    -H 'Accept: application/vnd.v1+json' \
    -H 'Authorization: Bearer token' \
    https://whatsapp.turn.io/v1/labels
> '
{
    "labels": [{
        "uuid": "the-uuid",
        "value": "the-label-name",
        "color": "the-label-color",
    }]
}

Performing an HTTP GET request with our vendor specific Accept: application/vnd.v1+json header unlocks this functionality. Return all labels currently linked to a number in a single result. We may be adding pagination in the future.

Retrieving Labelled Messages

$ curl -X GET \
    -H 'Accept: application/vnd.v1+json' \
    -H 'Authorization: Bearer token' \
    https://whatsapp.turn.io/v1/labels/<label-uuid>/messages
> '
{
    "has_more": true,
    "next": "/v1/labels/<label-uuid>/messages?p=1",
    "message_labels": [{
        "confidence": 0.8,
        "metadata": {},
        "deleted": false,
        "message": {
            // .. the message payload
        }
    }]
}

Performing an HTTP GET request with our vendor specific Accept: application/vnd.v1+json header unlocks this functionality. We return messages in batches of 50. The response will inform whether or not there are more results available to retrieve.

Linking an outbound message to an inbound

$ curl -X POST "https://whatsapp.turn.io/v1/messages" \
  -H "Authorization: Bearer token" \
  -H "X-Turn-In-Reply-To: some-earlier-external-id" \
  -H "Content-Type: application/json" \
  -d '
    {
        ...
    }'

It is often desirable to be able to link outbound messages to inbound messages for auditing, analysis, or the training of machine learning models purposes.

To do so add our vendor specific X-Turn-In-Reply-To HTTP header with the external id you want linked. We do not validate this value, it stored on file as is.

When you receive an outbound message via the webhooks, you can now retrieve the message that the outbound was a reply to.

Archiving a Chat

$ curl -X POST "https://whatsapp.turn.io/v1/chats/<wa-id>/archive" \
    -H "Authorization: Bearer token" \
    -H "Accept: application/vnd.v1+json" \
    -H "Content-Type: application/json" \
    -d '
    {
        "before": "the-message-id",
        "reason": "the reason for archiving"
    }
    '

It can be useful to archive a chat if it has been processed in an automated fashion. To do so the chat archive endpoint expects a before parameter that references a message id and a reason parameter that has a text reason for the archive operation.

The chat will only be archived if the last inbound message received in the chat is the same message as the before parameter references. If successful the archive endpoint will return an HTTP 201 Created response with the updated chat object.

If the last message received does not match the before then the endpoint will return an HTTP 200 OK response with the chat object unchanged.

Labelling a Message

$ curl -X POST "https://whatsapp.turn.io/v1/messages/<message-id>/labels" \
    -H "Authorization: Bearer token" \
    -H "Accept: application/vnd.v1+json" \
    -H "Content-Type: application/json" \
    -d '{
        "labels": ["compliment", "other", {"label": "question", "confidence": 0.9}]
    }'

The API allows one to add labels to messages, these are then automatically also promoted to the chat that the message is part of, and displayed in the UI in realtime. The labels attribute can contain a combination of strings of label names and/or JSON objects with a label and a confidence attribute. The confidence indicator is typically derived based on text analysis and can be stored together with the label. Note that the confidence values are not shown the user inferface, and are only available via the API.

Any labels provided that have not been defined in the UI will be ignored.

Creating Message Templates

$ curl -X POST "https://whatsapp.turn.io/graph/v3.3/<your phone number without leading +>/message_templates" \
  -d 'access_token=<whatsapp-api-token>' \
  -d 'category=ISSUE_RESOLUTION' \
  -d 'name=test_template' \
  -d 'language=en_US' \
  -d 'components=[
    {
      "type": "HEADER",
      "format": "TEXT",
      "text": "Welcome {{1}}",
      "example": {
         "header_text": ["Jane", "John"]
       }
    },
    {
      "type": "BODY",
      "text": "Hi {{1}}, you are now registered as a {{2}}.",
      "example":{
          "body_text": [
            ["Jane", "beta tester"],
            ["John", "support hero"]
          ]
       }
    },
    {
      "type": "FOOTER",
      "text": "Thanks again for signing up!"
    }
  ]'

The message templates API allows one to create message templates for approval by Facebook. It is a REST API that works the same as Facebook's own Graph API as documented at developers.facebook.com.

The payload can also be submitted as JSON and using normal Authorization: Bearer ... type authentication. We're allowing the form based post and access_token based authentication to ensure compatibility.

Keep in mind that if you do use JSON to submit the payload then the components field needs to be a JSON encoded string inside your JSON payload.

The name field is limited to 512 characters. The content field is limited to 1024 characters.

Possible values for status are:

Valid category codes are:

Valid language codes are:

Listing Message Templates

$ curl -X GET "https://whatsapp.turn.io/graph/v3.3/<your phone number without leading +>/message_templates?access_token=<whatsapp-api-token>"

You can use the WhatsApp Business Management API to get a list of your existing message templates.

This API mimics the behavior of the Facebook Graph Message Templates API. For more details please check the official documentation at developers.facebook.com.

Media Message Templates

$ curl -X POST "https://whatsapp.turn.io/graph/v3.3/<your phone number without leading +>/message_templates" \
  -d 'access_token=<whatsapp-api-token>' \
  -d 'category=ISSUE_RESOLUTION' \
  -d 'name=test_media_template' \
  -d 'language=en_US' \
  -d 'components=[
    {
      "type": "HEADER",
      "format": "IMAGE",
      "example": {
        "header_handle": ["example-file-handle", "another-example-file-handle"]
      }
    },
    {
      "type": "BODY",
      "text": "Hi {{1}}, you are now registered as a {{2}}.",
      "example":{
          "body_text": [
            ["Jane", "beta tester"],
            ["John", "support hero"]
          ]
       }
    }
  ]'

The message templates API also allows creating media templates, which are templates that include a media element in the header.

WhatsApp supports three types of media headers for templates:

This API mimics the behavior of the Facebook Graph Message Templates API. For more details please check the official documentation at developers.facebook.com.

Example Media

To facilitate the approval of your template by Facebook, you should provide one or more example media files that exemplify the type of images/video/documents that will be used when sending messages based on the template.

To do that, you first need to upload the example media(s) using the Resumable Upload API and then provide the file handle(s) returned by that API inside the example -> header_handle field of the HEADER component (see example on the right).

Interactive Message Templates

$ curl -X POST "https://whatsapp.turn.io/graph/v3.3/<your phone number without leading +>/message_templates" \
  -d 'access_token=<whatsapp-api-token>' \
  -d 'category=ISSUE_RESOLUTION' \
  -d 'name=test_interactive_template' \
  -d 'language=en_US' \
  -d 'components=[
    {
      "type": "HEADER",
      "format": "TEXT",
      "text": "Welcome {{1}}",
      "example": {
         "header_text": ["Jane", "John"]
       }
    },
    {
      "type": "BODY",
      "text": "Hi {{1}}, you are now registered as a {{2}}.",
      "example":{
          "body_text": [
            ["Jane", "beta tester"],
            ["John", "support hero"]
          ]
       }
    },
    {
      "type": "FOOTER",
      "text": "Thanks again for signing up!"
    },
    {
      "type": "BUTTONS",
      "buttons":[
        {
          "type": "PHONE_NUMBER",
          "text": "Call us",
          "phone_number": "+1(650) 555-1111"
        },
        {
          "type": "URL",
          "text": "Visit out website",
          "url": "https://www.website.com/{{1}}",
          "example": ["https://www.website.com/welcome", "https://www.website.com/register"]
        }
      ]
    }
  ]'

The message templates API also allows creating interactive templates. Interactive templates are message templates that include a component of type BUTTONS, which includes a list of buttons.

WhatsApp supports three types of buttons:

This API mimics the behavior of the Facebook Graph Message Templates API. For more details please check the official documentation at developers.facebook.com.

Resumable Upload API

$ curl -X POST "https://whatsapp.turn.io/graph/v3.3/app/uploads" \
  -d 'number=<your phone number without leading +>' \
  -d 'access_token=<whatsapp-api-token>' \
  -d 'file_length=<file-length-in-bytes>' \
  -d 'file_type=<file-mime-type>'

> {"id":"<upload-session-id>"}

# The previous API call returns an <upload-session-id>,
# which is needed as part of the URL for the next call

$ curl "https://whatsapp.turn.io/graph/<upload-session-id>" \
  -F 'number=<your phone number without leading +>' \
  -F 'access_token=<whatsapp-api-token>' \
  -F 'file=@"/path/to/media/file.extension"' \
  -H 'file_offset: 0'

> {"h":"<file-handle>"}

# The previous API call returns a <file-handle>,
# which can be used when creating a media template

The Resumable Upload API allows uploading media files to Facebook and returns file handles that can be used to specify examples when creating media templates.

The upload happens in two separate phases:

  1. A first API call is needed to initiate the upload session. An upload-session-id (a string similar to upload:###?sig=###) is returned to be used in the second step.
  2. A second API call is needed to upload the media file via multipart/form-data using the upload session that was just initiated.

The second API call uploads the media file to Facebook and returns an ID referred to as file handle (a string similar to 4::###).

When specifying the file type, please use a standard MIME type such as image/jpeg, video/mp4, etc.

Conversation Claiming

Turn allows one to delegate the control of a conversation with an individual to an external system for a limited amount of time.

This can be done in the Automation section in the Turn UI.

To ensure a webhook is suitable for use with conversation claiming, first add it as a normal webhook under the Webhooks settings, have it marked as enabled but not set to receive any inbound or outbound messages.

This ensures that the webhook is registered in Turn but is not automatically receiving any messaging traffic.

Next, set up the automation by selecting an appropriate trigger. For Step 2, Add an Action select the Call a wehbook action.

In Step 3 select the webhook that is meant to receive the inbound traffic as a result of a specific action trigger happening.

$ curl -X POST "https://whatsapp.turn.io/v1/messages" \
  -H "Authorization: Bearer token" \
  -H "X-Turn-Claim-Extend: <claim-uuid>" \
  -H "Content-Type: application/json" \
  -d '
    {
        "preview_url": false | true,
        "to": "whatsapp-id",
        "type": "text",
        "text": {
            "body": "your-text-message-content"
        }
    }'

> {
  "messages": [{
    "id": "gBEGkYiEB1VXAglK1ZEqA1YKPrU"
  }]
}
$ curl -X POST "https://whatsapp.turn.io/v1/messages" \
  -H "Authorization: Bearer token" \
  -H "X-Turn-Claim-Release: <claim-uuid>" \
  -H "Content-Type: application/json" \
  -d '
    {
        "preview_url": false | true,
        "to": "whatsapp-id",
        "type": "text",
        "text": {
            "body": "your-text-message-content"
        }
    }'
> {
  "messages": [{
    "id": "gBEGkYiEB1VXAglK1ZEqA1YKPrU"
  }]
}
$ curl -X POST "https://whatsapp.turn.io/v1/messages/<message-id>/automation" \
  -H "Authorization: Bearer token" \
  -H "X-Turn-Claim-Release: <claim-uuid>"

> {}

claiming screenshot

When the trigger fires, a lease will be created for the conversation in question.

Your webhook will receive an HTTP request with an X-Turn-Claim: <claim-uuid> header set. This lease lasts for a maximum of 5 minutes.

The webhook has 5 minutes to reply, otherwise Turn will expire the lease and reclaim control over the conversation.

The webhook can do 2 things with this lease for outbound messages posted to the /v1/messages endpoint:

It can set the X-Turn-Claim-Extend: <claim-uuid> HTTP request header to extend the claim for another 5 minutes.

It can set the X-Turn-Claim-Release: <claim-uuid> HTTP request header to release the claim and delegate control back to Turn.

Finally, there is a use case where the final response would want to submit the last message submitted back to Turn for re-evaluation by the Automation rules.

Submitting an HTTP POST to /v1/messages/<message-id>/automation with the X-Turn-Claim-Release: <uuid> HTTP header set results in the release of the claim and resubmitting the message to Turn for evaluation for Automation.

The use case here is where the webhook claiming the conversation can provide a fallback to Turn for functionality like "Reply 📌 Menu to return to the main menu".

Managing Content

Turn allows content, FAQs and Automators, to be imported and exported via the API. This can be useful for translation services or adding turn to your own information pipeline.

Exporting Content

$ curl -X GET "https://whatsapp.turn.io/v1/export" \
    -H "Authorization: Bearer token" \
    -H "Accept: application/vnd.v1+json" > content.json

By default, the response will only include FAQ and Automator items which have not been flagged as deleted in the database through the is_deleted property. If the consumer wishes to include deleted items as well, it is possible to specify the include_deleted query string parameter:

$ curl -X GET "https://whatsapp.turn.io/v1/export?include_deleted=true" \
    -H "Authorization: Bearer token" \
    -H "Accept: application/vnd.v1+json" > content.json

The response from this API can be used as a valid input for the import APIs described below.

Importing Content

When using HTTP POST the existing content is deleted and only the content present in the import will be available once it has completed. If the import fails, no content will be deleted.

$ curl -X POST "https://whatsapp.turn.io/v1/import" \
  -H "Authorization: Bearer token" \
  -H "Content-Type: application/json" \
  -H "Accept: application/vnd.v1+json" \
  -d @content.json

Updating Content

When using HTTP PUT the existing content is NOT deleted. Content will be updated if a matching entry with the same UUID is found. Entries present in the import without a UUID will be created. If the import fails, no content will be modified.

$ curl -X PUT "https://whatsapp.turn.io/v1/import" \
  -H "Authorization: Bearer token" \
  -H "Content-Type: application/json" \
  -H "Accept: application/vnd.v1+json" \
  -d @content.json

This endpoint can also be used to delete and undelete FAQ and Automator data, by modifying the is_deleted property of the items with a corresponding UUID.

Turn Context

The Turn context automatically surfaces information specific to the ongoing conversation from integrated systems via the integrations API. The Turn context is not stored, it is a real time system. As conversations unfold, integrations are polled for supplementary information on the conversation members which may be relevant to support the conversation's goals. This information is displayed in the Turn in realtime.

Turn Integration Library

We've released a Javascript library to help with the development of Turn integrations.

It is available on GitHub at turnhub/turnio-integration or you can install it with

$ npm install @turnio/integration
$ yarn install @turnio/integration

Below is a sample integration that does the following:

  1. Exposes a table and an ordered list in the context details panel.
  2. Suggests password reset content in the reply box
  3. Exposes an menu item that allows one to change a language.
const TurnIntegration = require('@turnio/integration')

const app = new TurnIntegration(process.env.SECRET)
  .context('Language', 'table', ({ chat, messages }) => ({
    Language: 'English',
    Confidence: 'Very high'
  }))
  .context('A list of things', 'ordered-list', ({ chat, messages }) => [
    'first item',
    'second item',
    'third item'
  ])
  .suggest(({ chat, messages }) => [
    {
      type: 'TEXT',
      title: 'Password reset',
      body: 'To reset your password click the link on the login page.',
      confidence: 0.4
    }
  ])
  .action(({ chat, messages }) => [
    {
      description: 'Change Language',
      payload: {
        really: 'yes'
      },
      options: {
        afr_ZA: 'Afrikaans',
        eng_ZA: 'English',
        zul_ZA: 'Zulu'
      },
      callback: ({ message, option, payload: { really } }, resp) => {
        console.log({ message, option, really })
        // Notify the frontend to refresh the context by setting
        // the response header
        resp.setHeader('X-Turn-Integration-Refresh', 'true')
        // this is return as JSON in the HTTP response
        return { ok: 'done' }
      }
    }
  ])
  .serve()

const port = process.env.PORT || 3000

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

Handshake

{
  "version": "1.0.0-alpha",
  "capabilities": {
    "actions": True,
    "suggested_responses": True,
    "context_objects": [
      {
        "title": "The title",
        "code": "details",
        "type": "table"
      }
    ]
  }
}

The handshake needs to be performed after the integration is configured. This is a call to the context url with a handshake query parameter set to true.

Turn context objects can be one of two types, either an ordered-list or a table. The type key is used to identify them.

Every context object has the following keys:

Endpoints need to supply a context_objects key which contains a key for each context object configured by the handshake. The order of the context objects in the context panel is determined by the order they are supplied in the handshake.

Retrieving the context, actions and suggestions

When a conversation loads in the user interface, the browser will initiate a request to the integration requesting an update.

The integration will hit your integration url with an HTTP POST request with the following payload:

{
    "chat": {
        "owner": "<the phone number>",
        "state": "<the state of the chat>",
        ...
    },
    "messages": [{
        // the 10 most recent inbound and outbound messages
        // that are part of this conversation
    }]
}

Context Updates

When receiving the HTTP POST request, you can supply context object updates under the context_objects key as per the formats below.

Ordered lists

{
  "version": "1.0.0-alpha",
  "context_objects": {
    "details": ["first time", "second item", "third item"]
  },
  "actions": {}
}

Ordered lists have a list as a payload. They're automatically prefixed with numbers, starting with 1. They do not support any kind of nesting. The key inside context_objects must match the code in your handshake return.

Tables

{
  "version": "1.0.0-alpha",
  "context_objects": {
    "details": {
      "Language": "English",
      "Confidence": "Very High"
    }
  },
  "actions": {}
}

Tables have a dictionary as a payload. The dictionary key is printed in bold text, the dictionary value is printed as normal. The key inside context_objects must match the code in your handshake return.

Suggested Responses

When receiving the HTTP POST request, you can supply suggested responses under the suggested_responses key as per the format below.

{
  "version": "1.0.0-alpha",
  "context_objects": {},
  "actions": {},
  "suggested_responses": [
    {
      "type": "TEXT",
      "title": "Password Reset",
      "body": "To reset your password click the link on the login page.",
      "confidence": 0.4
    }
  ]
}

Suggested responses can be specified and will be displayed in a select menu above the response textbox. They will be ordered by confidence

Suggested responses have the following keys:

Actions

When receiving the HTTP POST request, you can supply custom actions under the actions key as per the format below.

{
  "version": "1.0.0-alpha",
  "context_objects": {},
  "actions": {
    "change_language": {
      "description": "Change Language",
      "url": "/api/v1/turn/action",
      "payload": {
        "really": "yes"
      },
      "options": {
        "afr_ZA": "Afrikaans",
        "eng_ZA": "English",
        "zul_ZA": "Zulu"
      }
    }
  }
}

Actions can be specified and will be displayed in a dropdown above the context. They will POST the payload to the url specified on the configured integration when clicked.

Actions have the following keys:

Example callback JSON payload

{
  "address": "+16315551003",
  "integration_uuid": "the-uuid-of-the-integration",
  "integration_action_uuid": "the-uuid-of-the-action",
  "message": {
    // the message that was used to trigger this action
  },
  "option": "afr_ZA",
  "payload": {
    "really": "yes"
  }
}

Refreshing the context and actions

There's a good chance you would want to have the UI refresh the available context and actions after having completed an action.

Immediate refresh

One can force an immediate refresh of the context and actions by returning the X-Turn-Integration-Refresh header with a value of true.

There's an example Turn Integration Demo app that shows how to achieve this. The demo application exposes an opt-in or opt-out action depending on the state. Once opted in the context is refreshed to show the new state and the action is refreshed to display only an opt-out option.

Delayed refresh

Using the integration_uuid and the integration_action_uuid one can make an HTTP request to the integration events endpoint. This will trigger Turn to poll your integration and refresh the UI based on the information returned.

Hitting the integration events endpoint

$ curl -X POST "https://whatsapp.turn.io/api/integrations/<your-integration_uuid>/notify/finish" \
    -H "Content-Type: application/json" \
    -d '{
        "integration_action_uuid": "<your-integration-action-uuid>"
    }'

Turn Fallback Channel

The fallback channel allows you to send non-WhatsApp messages using Turn, it is enabled by specifying a fallback url in the UI.

Contacts will be defaulted to the fallback channel when the amount of WhatsApp failures reach the error threshold configured in the UI. A successful incoming WhatsApp message from the contact would change the default back to WhatsApp.

Example payload

{
  "preview_url": false,
  "recipient_type": "individual",
  "to": "16315551003",
  "type": "text",
  "text": {
    "body": "text message content"
  }
}

Override outgoing message

You can specify a header to force a message to be sent over the fallback channel, this will send the message payload to the fallback channel regardless of default channel on the contact.

$ curl -X POST "https://whatsapp.turn.io/v1/messages" \
  -H "Authorization: Bearer token" \
  -H "Content-Type: application/json" \
  -H "x-turn-fallback-channel: 1"
  -d '
    {
        "preview_url": false | true,
        "to": "whatsapp-id",
        "type": "text",
        "text": {
            "body": "your-text-message-content"
        }
    }'

Events

Fallback channel events can be sent to https://whatsapp.turn.io/api/whatsapp/channel-uuid in the format specified here: https://developers.facebook.com/docs/whatsapp/api/webhooks/outbound

Manually updating flag

You can call the contact API to manually update the "is_fallback_channel_active" flag

See the Contact Api for more info

Security

See an example below of how the signature can be calculated using the Python programming language.

>>> import hmac
>>> import base64
>>> from hashlib import sha256
>>> h = hmac.new("secret", '{"foo":"bar"}', sha256)
>>> base64.b64encode(h.digest())
'PzqzmGtlarsXrz6xRD7WwI74//n+qDkVkJ0bQhrsib4='

The signature is sent as an HTTP header to the fallback channel endpoints you have configured.

X-Turn-Hook-Signature: PzqzmGtlarsXrz6xRD7WwI74//n+qDkVkJ0bQhrsib4=

The hmac secret should be used to calculate a signature for the message payload. Using this signature you can verify that the message was indeed received from Turn.

The signature is sent as an HTTP header in the webhook posted to the endpoints you have configured.

Turn Events API

The events API allows you to send external events to Turn, they will be displayed in the chat history.

External Message Example

To send a external message to Turn, do the following request:

$ curl -X POST "https://whatsapp.turn.io/v1/events" \
  -H "Authorization: Bearer token" \
  -H "Content-Type: application/json" \
  -H "accept: application/vnd.v1+json"
  -d '
    {
        "event_type": "external_message",
        "urn": "+16315551003",
        "timestamp": 1555493466000,
        "event_id": "1111117",
        "details": {
            "content": "Hi how are you?",
            "direction": "inbound",
            "from_addr": "+16315551003"
        }
    }'

Data Export API

The Data Export API allows you to search for messages in a given range of dates.

It is a two step approach:

  1. Create a cursor with the required parameters to start getting messages.
  2. Request data in a paginated fashion using the cursor.

Required parameters are:

- from date and time indicating the timestamp from which messages should be retrieved. - until date and time indicating the timestamp up to which messages should be retrieved.

Optional parameters are:

- ordering indicates if the result set should be ordered ascending or descending. - page_size indicates the number of messages wanted in the result set. - scrubbing_rules set of possible rules to remove sensitive information from specific fields in the JSON response. These rules can be applied to either inbound and outbound messages. The rules currently supported are a search and replace rule with support for Regular Expressions, and a hashing rule.

We return original WhatsApp messages but include our vendor specific _vnd key in each message payload with supplementary information Turn has on record.

Create a cursor

To create a cursor to start searching for messages, do the following request:

$ curl -X POST "https://whatsapp.turn.io/v1/data/messages/cursor" \
  -H "Authorization: Bearer token" \
  -H "Content-Type: application/json" \
  -H "accept: application/vnd.v1+json"
  -d '
    {
        "from": "2021-08-02T01:55:00.272Z",
        "until": "2021-08-20T01:55:00.272Z",
        "ordering": "desc",
        "page_size": 10,
        "scrubbing_rules": {
            "inbound": {
                "messages.text.body": {
                    "type": "regex",
                    "search": "world",
                    "replace": "XXXX"
                },
                "messages.from": {
                    "type": "hash"
                },
                "contacts.profile.name": {
                    "type": "hash"
                }
            },
            "outbound": {
                "text.body": {
                    "type": "regex",
                    "search": "world",
                    "replace": "XXXX"
                },
                "from": {
                    "type": "hash"
                }
            }
        }
    }'
> {
    "cursor": "<cursor>",
    "expires_at": "2021-08-30T17:47:18.318218Z"
}

Get messages

To retrieve messages, do the following request:

$ curl -X GET "https://whatsapp.turn.io/v1/data/messages/cursor/<cursor>" \
    -H 'Authorization: Bearer token' \
    -H "Accept: application/vnd.v1+json" \
> {
    "data": [
        {
            "contacts": [
                {
                    "profile": {
                        "name": "SFMyNTY.KzMxNjIzNDU2NzAw.GSl886dfZXJ70DkFsUy39ij4QgYSceBw5UdV76Ugek0"
                    },
                    "wa_id": "01234567891"
                }
            ],
            "messages": [
                {
                    "_vnd": {
                        "v1": {
                            "author": {},
                            "chat": {
                                "assigned_to": null,
                                "owner": "+01234567891",
                                "permalink": "https://whatsapp.turn.io/app/col/63f0f404-08ee-423e-b571-01de718d4683",
                                "state": "OPEN",
                                "state_reason": null,
                                "unread_count": 0,
                                "uuid": "63f0f404-08ee-423e-b571-01de718d4683"
                            },
                            "direction": "inbound",
                            "faq_uuid": null,
                            "in_reply_to": null,
                            "inserted_at": "2021-08-02T22:54:44.715101Z",
                            "labels": [],
                            "rendered_content": null
                        }
                    },
                    "from": "SFMyNTY.MzE2MjM0NTY3MDA.nbfRbPvYvAqpKMOy19SwjrFZCz0Jb4IIxxaIc_4rx7s",
                    "id": "PksTrDovmBIG8SK8",
                    "text": {
                        "body": "hello XXXX 0"
                    },
                    "timestamp": "1627944884",
                    "type": "text"
                }
            ]
        },
        {
            "_vnd": {
                "v1": {
                    "author": {},
                    "chat": {
                        "assigned_to": null,
                        "owner": "+01234567891",
                        "permalink": "https://whatsapp.turn.io/app/col/63f0f404-08ee-423e-b571-01de718d4683",
                        "state": "OPEN",
                        "state_reason": null,
                        "unread_count": 0,
                        "uuid": "63f0f404-08ee-423e-b571-01de718d4683"
                    },
                    "direction": "outbound",
                    "faq_uuid": null,
                    "in_reply_to": null,
                    "inserted_at": "2021-08-02T22:54:44.721779Z",
                    "labels": [],
                    "rendered_content": null
                }
            },
            "from": "SFMyNTY.MjcxMjM0NTY3MDA.JUHQXxo5G9gnokpMisKqGz2uzhmx106k49ZcI8k_qC0",
            "id": "h4UBvhwAdZvgwXhi",
            "preview_url": false,
            "recipient_type": "individual",
            "text": {
                "body": "hello XXXX 1"
            },
            "timestamp": "1627944884",
            "to": null,
            "type": "text"
        },
        ...
    ],
    "paging": {
        "expires_at": "2021-08-30T18:49:26.761537Z",
        "next": "<next cursor>"
    }
}

If there is more data on the next page, a next cursor will be provided to continue getting results. If not, it means you have reached all results for the given search criteria.