Send a reaction
Your application can add or remove a reaction on a specific message.
The endpoint is addressed by the target message’s message_id in the URL: there is no to field in the body, because the recipient is taken from the original message.
This page covers sending and removing reactions. To list the active reactions on a message, see Get reactions.
Before sending the reaction, Turbo Notify opens the conversation the way a person would: the number shows as online, the unread messages in the target message’s chat are marked as read, and only then is the reaction sent. See Mark as read.
Add a reaction
POST /v1/messages/{message_id}/react: adds a reaction to the message identified by {message_id} (format msg_{hex32}). This route acts on the main number. To react from an extra number, use the full form POST /v1/numbers/{ref}/messages/{message_id}/react with the number’s alias in the URL path.
curl -X POST \ https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "reaction": "🎉" }'const response = await fetch( 'https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ reaction: '🎉' }) });
console.log(response.status); // 202import requests
url = "https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react"headers = { "Authorization": "Bearer YOUR_KEY", "Content-Type": "application/json"}data = {"reaction": "🎉"}
response = requests.post(url, json=data, headers=headers)print(response.status_code) # 202package main
import ( "fmt" "net/http" "strings")
func main() { payload := strings.NewReader(`{"reaction": "🎉"}`) req, _ := http.NewRequest( "POST", "https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react", payload, ) req.Header.Set("Authorization", "Bearer YOUR_KEY") req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() fmt.Println(resp.StatusCode) // 202}<?php$url = 'https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react';$data = ['reaction' => '🎉'];
$options = [ 'http' => [ 'header' => [ 'Authorization: Bearer YOUR_KEY', 'Content-Type: application/json' ], 'method' => 'POST', 'content' => json_encode($data) ]];
$context = stream_context_create($options);file_get_contents($url, false, $context);var_dump($http_response_header);?>using System.Text;using System.Text.Json;
var client = new HttpClient();client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_KEY");
var data = new { reaction = "🎉" };var json = JsonSerializer.Serialize(data);var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync( "https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react", content);Console.WriteLine((int)response.StatusCode); // 202import java.net.http.*;import java.net.URI;
public class Reaction { public static void main(String[] args) throws Exception { var client = HttpClient.newHttpClient();
var json = """ {"reaction": "🎉"} """;
var request = HttpRequest.newBuilder() .uri(URI.create( "https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react")) .header("Authorization", "Bearer YOUR_KEY") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); // 202 }}Turbo Notify responds with an asynchronous acceptance:
{
"message_id": "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70",
"reaction_event_id": "op_reaction_a1b2c3d4e5f6",
"status": {
"state": "pending",
"at": "2025-07-16T10:00:00Z"
}
} Remove a reaction
To remove a reaction you sent earlier, send the reaction property as null (or an empty string) to the same endpoint.
curl -X POST \ https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "reaction": null }'const response = await fetch( 'https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ reaction: null }) });
console.log(response.status); // 202import requests
url = "https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react"headers = { "Authorization": "Bearer YOUR_KEY", "Content-Type": "application/json"}data = {"reaction": None}
response = requests.post(url, json=data, headers=headers)print(response.status_code) # 202package main
import ( "fmt" "net/http" "strings")
func main() { payload := strings.NewReader(`{"reaction": null}`) req, _ := http.NewRequest( "POST", "https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react", payload, ) req.Header.Set("Authorization", "Bearer YOUR_KEY") req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() fmt.Println(resp.StatusCode) // 202}<?php$url = 'https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react';$data = ['reaction' => null];
$options = [ 'http' => [ 'header' => [ 'Authorization: Bearer YOUR_KEY', 'Content-Type: application/json' ], 'method' => 'POST', 'content' => json_encode($data) ]];
$context = stream_context_create($options);file_get_contents($url, false, $context);var_dump($http_response_header);?>using System.Text;
var client = new HttpClient();client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_KEY");
var json = """{"reaction": null}""";var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync( "https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react", content);Console.WriteLine((int)response.StatusCode); // 202import java.net.http.*;import java.net.URI;
public class Reaction { public static void main(String[] args) throws Exception { var client = HttpClient.newHttpClient();
var json = """ {"reaction": null} """;
var request = HttpRequest.newBuilder() .uri(URI.create( "https://api.turbonotify.com/v1/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react")) .header("Authorization", "Bearer YOUR_KEY") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); // 202 }}Turbo Notify responds with an asynchronous acceptance:
{
"message_id": "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70",
"reaction_event_id": "op_reaction_9f8e7d6c5b4a",
"status": {
"state": "pending",
"at": "2025-07-16T10:00:05Z"
}
} The outcome arrives by webhook as message.reacted (success) or message.react.failed (failure). The failure payload includes the target message’s message_id, so you do not need request_id to match it.
Reacting from an extra number
To react from a registered, active extra number, put that number’s alias in the URL path. The recipient is still taken from the message_id.
curl -X POST \ https://api.turbonotify.com/v1/numbers/vendas-sp/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/react \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "reaction": "🎉" }'If the alias in the URL is not registered as an extra number, or the number is not active, the request is rejected.
Learn more about extra numbers.
Possible errors
| Status | Error code | Description |
|---|---|---|
400 | invalid_message_id | The message_id in the URL is not in the msg_{hex32} format. |
402 | feature_not_available | The feature is not available on your plan. |
401 | not_authenticated | The API key is invalid or missing. |
403 | insufficient_scope | The API key does not have the messages:send scope (the response includes required_scope). |
404 | number_not_found | The {ref} in the path does not match any number on the account (on the flat route: the account has no main number yet). |
400 | invalid_number_id | The value in the URL starts with num_ but is not a valid number id. |
404 | not_found | The message was not found, belongs to another organization, or belongs to a number other than the one in the URL (on the shortcut route without {ref}, the main number): all of these cases return the same response, with no distinction. |
409 | number_not_connected | The sending number is not connected to WhatsApp. Reconnect the number and try again. |
422 | message_not_reactable | The target message has not been sent to WhatsApp yet, or has already been deleted. |
422 | sender_not_found | The alias or number given does not match any sender on the account. |
422 | validation_error | The request body is malformed or failed validation. The response includes details.errors with the list of invalid fields. |
500 | internal_error | Unexpected internal server error. |
A message that does not exist and a message from another number return the same not_found, on both routes (the shortcut without {ref} and the nested /numbers/{ref}/messages/...). If you know the message exists but got not_found, check that you are using the id (or current alias) of the number that sent or received it.