Skip to main content

Webhooks

Webhooks are configurable via the Turn.io UI. They enable the automatic forwarding of messages from Turn to a service that you own or operate.

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 three subscription types are supported: whatsapp, turn and journey_outbound.

note

engage was a supported type but is deprecated and will soon be removed.

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.

warning

We recommend that anyone who receives messages via the Webhook, enforce a uniqueness check on the message id to prevent acting on double deliveries.

Turn.io Subscriptions

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

This is a subscription type that is generated for messages created through the messaging API that Turn.io 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-Id is 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.

note

Note that these messages are only dispatched after the WhatsApp Enterprise Client has successfully accepted the message for delivery.

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 an 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",
"status": "sent",
"timestamp": "1518694700",
"message": {
"recipient_id":"16315555555"
}
}
]
}

Message was read

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

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.
  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.
note

For a status to be read, it must have been delivered. In some scenarios, such as when a user is in the chat screen and a message arrives, the message is delivered and read almost simultaneously. In this or other similar scenarios, the delivered notification will be discarded, as it is implied with that a message has been delivered if it has been read. The reason for this behavior is internal optimization.

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 calculate a signature for the webhook payload. Using this signature you can verify that the webhook was indeed received from Turn.io.

note

Currently only SHA256 is supported as an hmac digest

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"
},
{
...
}
]
}

Webhook Performance and Retries

Webhook Performance

warning

Turn enforces a strict 5 second timeout on webhooks. If the service fails to respond within 5 seconds, Turn will retry up to 5 times, with an incremental backoff. See documentation below on retries.

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

Turn.io 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.

QueueMillisecond threshold
low1 second or more
defaultLonger than 200ms but less than 1 second
high200ms or less

Turn.io'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.

note

We may choose to expose more application settings functionality in the future. Please get in touch with us should you feel anything critical is missing.

Webhook Retry Strategy

Turn.io will rety to send a webhook event depending on the HTTP status code:

Error TypeBehaviorExample HTTP Response
4XX HTTP Status Codes (Client Errors)Cancel further retriesHTTP 404 Not Found
Unresolvable Domain Errors (Permanent)Cancel further retriesHTTP 502 Bad Gateway (Unresolvable Domain)
Network Errors (Temporary)RetryHTTP 504 Gateway Timeout
Other ErrorsRetryHTTP 500 Internal Server Error

This ensures that retriable errors are handled efficiently, while unrecoverable ones are quickly identified and canceled, optimizing the system’s response to different failure scenarios.

Turn.io will continue to retry it for a maximum of 5 times. Turn.io does this with an incremental backoff, meaning there is an increased delay in every retry.

The first retry follows roughly 17 seconds after the first failed attempt. The following retries occur about 19 seconds, 24, 31 and then 47 seconds later. Messages will be dropped if the endpoint is offline longer than about 140 seconds.

Some amount of jitter is introduced in the retry timings and so the exact timing interval may differ at times.