Editar e apagar mensagens
As duas operações agem sobre uma mensagem já enviada por você. O message_id é o mesmo msg_{hex32} retornado pelo envio original ou visto em webhooks/polling. As duas retornam 202 Accepted imediatamente; o resultado real chega de forma assíncrona via webhook.
Use POST /v1/messages/{id}/edit e POST /v1/messages/{id}/delete para o número principal. Para números extras, use POST /v1/numbers/{ref}/messages/{id}/edit e .../delete.
Escopo obrigatório: messages:send
Antes de editar ou excluir, o Turbo Notify abre a conversa como uma pessoa faria: o número aparece online, o histórico de mensagens não lidas do chat de origem da mensagem-alvo é marcado como lido e só então a edição ou a exclusão acontece. Veja Marcar como lida.
O que pode ser alterado depois do envio
| Mensagem | Editar | Apagar para todos |
|---|---|---|
| Texto | Sim. Envie o novo text. | Sim |
| Imagem, vídeo ou documento | Sim, só a legenda. Envie a nova caption. | Sim |
| Evento de calendário | Sim, o evento inteiro é substituído, ou o evento é cancelado. | Sim |
| Localização, cartão de contato, botão de link, figurinha ou áudio | Não | Sim, é a única alteração |
O arquivo de uma imagem, vídeo ou documento nunca muda. Para mandar outro arquivo, envie uma mensagem nova. Só mensagens enviadas pela API por este número podem ser alteradas (origin: "tn").
Texto e legenda só podem ser alterados até 15 minutos depois do envio, que é o prazo do WhatsApp. Depois disso a API responde 400 edit_window_expired e a saída é enviar uma mensagem nova. Os 15 minutos não valem para evento de calendário, embora o WhatsApp ainda possa recusar uma edição de evento (o desfecho chega como message.edit.failed).
Editar mensagem
Substitui o texto de uma mensagem de texto já enviada (edição nativa do WhatsApp: o histórico da conversa mostra “editada”). Para trocar a legenda de uma imagem, vídeo ou documento, veja a seção da legenda. Para editar ou cancelar um evento de calendário, veja a seção do evento.
curl -X POST \ https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/edit \ -H "Authorization: Bearer SUA_CHAVE" \ -H "Content-Type: application/json" \ -d '{ "text": "Texto corrigido da mensagem." }'const messageId = 'msg_01993c5a1b24d3e9f01a2b3c4d5e6f70';const response = await fetch( `https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/${messageId}/edit`, { method: 'POST', headers: { 'Authorization': 'Bearer SUA_CHAVE', 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Texto corrigido da mensagem.' }) });
const result = await response.json();console.log(result);import requests
message_id = "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70"url = f"https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/{message_id}/edit"headers = { "Authorization": "Bearer SUA_CHAVE", "Content-Type": "application/json"}data = {"text": "Texto corrigido da mensagem."}
response = requests.post(url, json=data, headers=headers)result = response.json()print(result)package main
import ( "fmt" "io" "net/http" "strings")
func main() { payload := strings.NewReader(`{"text": "Texto corrigido da mensagem."}`) req, _ := http.NewRequest( "POST", "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/edit", payload, ) req.Header.Set("Authorization", "Bearer SUA_CHAVE") req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body))}<?php$messageId = 'msg_01993c5a1b24d3e9f01a2b3c4d5e6f70';$url = "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/{$messageId}/edit";$data = ['text' => 'Texto corrigido da mensagem.'];
$options = [ 'http' => [ 'header' => [ 'Authorization: Bearer SUA_CHAVE', 'Content-Type: application/json' ], 'method' => 'POST', 'content' => json_encode($data) ]];
$context = stream_context_create($options);$result = file_get_contents($url, false, $context);$response = json_decode($result, true);var_dump($response);?>using System.Text;using System.Text.Json;
var client = new HttpClient();client.DefaultRequestHeaders.Add("Authorization", "Bearer SUA_CHAVE");
var messageId = "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70";var data = new { text = "Texto corrigido da mensagem." };var json = JsonSerializer.Serialize(data);var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync( $"https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/{messageId}/edit", content);var result = await response.Content.ReadAsStringAsync();Console.WriteLine(result);import java.net.http.*;import java.net.URI;
public class EditarMensagem { public static void main(String[] args) throws Exception { var client = HttpClient.newHttpClient(); var messageId = "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70";
var json = """ {"text": "Texto corrigido da mensagem."} """;
var request = HttpRequest.newBuilder() .uri(URI.create( "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/" + messageId + "/edit")) .header("Authorization", "Bearer SUA_CHAVE") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); }}Corpo da requisição
| Campo | Tipo | Obrigatório | Notas |
|---|---|---|---|
text | string | sim | 1 a 4.096 caracteres. Único campo aceito: corpo estrito (qualquer outro campo retorna 422). |
Resposta (202)
{
"message_id": "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70",
"status": {
"state": "pending",
"at": "2026-06-01T13:05:01Z",
"error": null
}
} O resultado real chega via webhook message.edited (sucesso) ou
message.edit.failed (falha, com objeto error). Após a edição,
GET /v1/numbers/{ref}/messages/{id} já retorna o texto atualizado.
Menções em edições
O novo text da edição (e a nova caption, veja Editar legenda) também passa pela detecção de menções @<numero>,
com as mesmas regras de Menções (@mentions).
A edição substitui todo o conjunto de menções da mensagem: o mentions
retornado por GET /v1/numbers/{ref}/messages/{id} (e o data.mentions do
webhook message.edited) passa a refletir só os tokens presentes no texto
novo. Uma edição que remove todos os @<numero> do texto original faz
mentions desaparecer da resposta.
Editar legenda
Substitui a legenda de uma imagem, vídeo ou documento que o seu número enviou. Use a mesma rota de edição e mande caption no lugar de text. Quem recebe vê a legenda nova marcada como “editada”. O arquivo continua como estava e a mensagem mantém o id.
curl -X POST \ https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/edit \ -H "Authorization: Bearer SUA_CHAVE" \ -H "Content-Type: application/json" \ -d '{ "caption": "Fatura de outubro, total corrigido." }'const messageId = 'msg_01993c5a1b24d3e9f01a2b3c4d5e6f70';const response = await fetch( `https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/${messageId}/edit`, { method: 'POST', headers: { 'Authorization': 'Bearer SUA_CHAVE', 'Content-Type': 'application/json' }, body: JSON.stringify({ caption: 'Fatura de outubro, total corrigido.' }) });
console.log(await response.json());import requests
message_id = "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70"response = requests.post( f"https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/{message_id}/edit", headers={ "Authorization": "Bearer SUA_CHAVE", "Content-Type": "application/json", }, json={"caption": "Fatura de outubro, total corrigido."},)print(response.json())Corpo da requisição (legenda)
| Campo | Tipo | Obrigatório | Notas |
|---|---|---|---|
caption | string | sim | 1 a 1.024 caracteres. Substitui a legenda inteira e não pode ser vazia. Aceita as mesmas menções @<numero> da legenda de um envio. Único campo aceito: qualquer outro campo retorna 422. |
Resposta e resultado
A resposta é a mesma de qualquer edição: 202 com o message_id e o status pending. O desfecho chega por webhook (ou por polling):
message.editedquando a legenda foi alterada. Traz ocontent_typeda mensagem (image,videooudocument) e a legenda nova empreview(até 50 caracteres, como no texto).message.edit.failedquando a edição não foi feita.
Legendas editadas no app do WhatsApp também chegam como message.edited, com origin: "native", o content_type do arquivo e a legenda nova em preview.
A edição não consome a cota de mensagens. Um corpo de legenda sobre um texto, um corpo de texto sobre uma imagem, ou qualquer corpo sobre uma mensagem que não pode ser editada responde 400 message_not_editable.
Editar ou cancelar um evento de calendário
Um evento de calendário que o seu número enviou (veja Enviar evento de calendário) também pode ser editado ou cancelado. Use a mesma rota de edição e mande o corpo do evento no lugar de text. O campo name indica que o corpo é um evento.
A edição substitui o evento inteiro: o que você não enviar sai do evento. Por isso name e start_at são obrigatórios, como no envio, e uma edição sem location remove o local.
curl -X POST \ https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/edit \ -H "Authorization: Bearer SUA_CHAVE" \ -H "Content-Type: application/json" \ -d '{ "name": "Sprint Planning", "description": "Planejamento da sprint 24. Sala trocada.", "start_at": "2026-11-05T15:00:00Z", "end_at": "2026-11-05T16:00:00Z", "join_link": "https://meet.example.com/abc", "location": { "latitude": -23.5505, "longitude": -46.6333, "name": "HQ - Sala Azul" } }'const messageId = 'msg_01993c5a1b24d3e9f01a2b3c4d5e6f70';const response = await fetch( `https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/${messageId}/edit`, { method: 'POST', headers: { 'Authorization': 'Bearer SUA_CHAVE', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Sprint Planning', description: 'Planejamento da sprint 24. Sala trocada.', start_at: '2026-11-05T15:00:00Z', end_at: '2026-11-05T16:00:00Z', join_link: 'https://meet.example.com/abc', location: { latitude: -23.5505, longitude: -46.6333, name: 'HQ - Sala Azul' } }) });
console.log(await response.json());import requests
message_id = "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70"response = requests.post( f"https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/{message_id}/edit", headers={ "Authorization": "Bearer SUA_CHAVE", "Content-Type": "application/json", }, json={ "name": "Sprint Planning", "description": "Planejamento da sprint 24. Sala trocada.", "start_at": "2026-11-05T15:00:00Z", "end_at": "2026-11-05T16:00:00Z", "join_link": "https://meet.example.com/abc", "location": { "latitude": -23.5505, "longitude": -46.6333, "name": "HQ - Sala Azul", }, },)print(response.json())Corpo da requisição (evento)
Os campos são os mesmos do envio de evento de calendário, sem o objeto content, mais canceled.
| Campo | Tipo | Obrigatório | Notas |
|---|---|---|---|
name | string | sim | título do evento; até 200 caracteres (campo âncora do corpo de evento) |
start_at | datetime | sim | ISO-8601 com timezone |
end_at | datetime | não | ISO-8601 com timezone; deve ser > start_at |
description | string | não | até 2000 caracteres |
location | object | não | mesmo formato do objeto location do envio |
join_link | string | não | apenas http:///https:// |
has_reminder | bool | não | padrão false |
reminder_offset_sec | int | não | segundos antes do evento; só tem efeito com has_reminder=true |
extra_guests_allowed | bool | não | padrão false |
canceled | bool | não | padrão false. Com true, o evento é cancelado. |
Cancelar o evento
Mande "canceled": true no mesmo corpo. O evento continua na conversa, aparece como cancelado e os convidados são avisados pelo WhatsApp.
curl -X POST \ https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/edit \ -H "Authorization: Bearer SUA_CHAVE" \ -H "Content-Type: application/json" \ -d '{ "name": "Sprint Planning", "start_at": "2026-11-05T15:00:00Z", "canceled": true }'Resposta e resultado
A resposta é a mesma de qualquer edição: 202 com o message_id e o status pending. O desfecho chega por webhook (ou por polling):
message.editedquando o evento foi alterado. Trazcontent_type: "calendar", o nome do evento empreviewecanceled: truesó quando o evento foi cancelado.message.edit.failedquando a edição não foi feita. Comerror.codeigual amessage_secret_unavailable, este número não pode mais alterar o evento, por exemplo porque foi vinculado de novo depois do envio. Envie um novo evento com os novos dados. Comcalendar_edit_unavailable, a plataforma não consegue alterar eventos no momento e repetir o pedido não resolve: envie um novo evento com os novos dados. O WhatsApp também pode recusar a edição depois de algum tempo, e nesse caso o desfecho é o mesmomessage.edit.failed.
Os exemplos de cada evento estão em Eventos de calendário.
Regras
- Só um evento enviado por este número pode ser editado, e só nos status
sent,deliveredouread. - Um corpo que não combina com a mensagem (texto sobre um evento de calendário, evento sobre um texto, legenda sobre um texto) responde
400 message_not_editable. - Uma mensagem de outro número responde
404 not_found. - A edição não consome a cota de mensagens.
O que acontece com os convidados
| Situação | O que fazer |
|---|---|
| Correção: erro de digitação, descrição ou link | Edite o evento. O cartão mostra a mudança e quem já respondeu continua com a resposta. |
| Data, horário ou local mudaram e os convidados precisam aceitar | Cancele o evento e envie um novo. A mensagem nova chama a atenção do convidado, e a resposta antiga (“Vou”) não vale para ela. |
Nem a edição nem o cancelamento alteram a entrada que o convidado criou na agenda dele com o botão “Adicionar ao calendário”: ela continua na data antiga e não aparece como cancelada, e adicionar de novo cria uma segunda entrada. Peça ao convidado que apague a entrada antiga.
Apagar (revogar) mensagem
Revoga uma mensagem já enviada (equivalente a “Apagar para todos” no WhatsApp). O corpo da requisição é vazio.
curl -X POST \ https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/delete \ -H "Authorization: Bearer SUA_CHAVE"const messageId = 'msg_01993c5a1b24d3e9f01a2b3c4d5e6f70';const response = await fetch( `https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/${messageId}/delete`, { method: 'POST', headers: { 'Authorization': 'Bearer SUA_CHAVE' } });
const result = await response.json();console.log(result);import requests
message_id = "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70"url = f"https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/{message_id}/delete"headers = {"Authorization": "Bearer SUA_CHAVE"}
response = requests.post(url, headers=headers)result = response.json()print(result)package main
import ( "fmt" "io" "net/http")
func main() { req, _ := http.NewRequest( "POST", "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/msg_01993c5a1b24d3e9f01a2b3c4d5e6f70/delete", nil, ) req.Header.Set("Authorization", "Bearer SUA_CHAVE") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body))}<?php$messageId = 'msg_01993c5a1b24d3e9f01a2b3c4d5e6f70';$url = "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/{$messageId}/delete";
$options = [ 'http' => [ 'header' => ['Authorization: Bearer SUA_CHAVE'], 'method' => 'POST' ]];
$context = stream_context_create($options);$result = file_get_contents($url, false, $context);$response = json_decode($result, true);var_dump($response);?>var client = new HttpClient();client.DefaultRequestHeaders.Add("Authorization", "Bearer SUA_CHAVE");
var messageId = "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70";var response = await client.PostAsync( $"https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/{messageId}/delete", null);var result = await response.Content.ReadAsStringAsync();Console.WriteLine(result);import java.net.http.*;import java.net.URI;
public class DeletarMensagem { public static void main(String[] args) throws Exception { var client = HttpClient.newHttpClient(); var messageId = "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70";
var request = HttpRequest.newBuilder() .uri(URI.create( "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages/" + messageId + "/delete")) .header("Authorization", "Bearer SUA_CHAVE") .POST(HttpRequest.BodyPublishers.noBody()) .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); }}Resposta (202)
{
"message_id": "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70",
"status": {
"state": "pending",
"at": "2026-06-01T13:06:01Z",
"error": null
}
} O resultado real chega via webhook message.deleted (sucesso) ou
message.delete.failed (falha, com objeto error).
Após uma exclusão bem-sucedida, GET /v1/numbers/{ref}/messages/{id} deixa
de retornar o conteúdo original tanto para quem apagou quanto para todo
destinatário da mensagem. O content_type da mensagem não muda: o que
muda é o conteúdo do campo estruturado correspondente:
content_type original | Campo afetado | Valor após a exclusão |
|---|---|---|
text | text | "" (string vazia) |
image / video / audio / document / sticker | image / video / audio / document / sticker | {"kind": "unavailable", "reason": "deleted"}: a mídia não fica mais acessível via URL assinada |
location | location | {"kind": "unavailable", "reason": "deleted"} |
calendar | calendar | {"kind": "unavailable", "reason": "deleted"} |
contact | contact | {"kind": "unavailable", "reason": "deleted"} |
interactive | interactive | {"kind": "unavailable", "reason": "deleted"} |
Todo tipo que não é texto usa o mesmo objeto, então “este conteúdo não está mais
disponível” tem um único formato, qualquer que seja o content_type.
Erros possíveis
Os dois endpoints compartilham o mesmo conjunto de erros:
| Status | Código de erro | Descrição |
|---|---|---|
400 | message_not_editable | (apenas na edição) A mensagem foi recebida (enviada por outra pessoa), foi enviada pelo próprio celular (origin: "native"), não é um texto, um evento de calendário nem a legenda de uma imagem, vídeo ou documento, o corpo não combina com a mensagem (texto sobre um evento, evento sobre um texto, legenda sobre um texto), ainda não foi enviada, já foi apagada, ou seu status atual (failed, por exemplo) não permite edição. Só podem ser editados textos, eventos de calendário e a legenda de uma imagem, vídeo ou documento. |
400 | edit_window_expired | (apenas na edição) Passaram mais de 15 minutos desde o envio de um texto ou de uma legenda, o prazo que o WhatsApp dá para alterá-los. Envie uma mensagem nova. Os 15 minutos não valem para evento de calendário. |
400 | message_not_deletable | (apenas na exclusão) A mensagem foi recebida (enviada por outra pessoa), foi enviada pelo próprio celular (origin: "native"), ainda não foi enviada, já foi apagada, ou seu status atual não permite exclusão. |
401 | not_authenticated | A chave de API está inválida ou ausente. |
403 | insufficient_scope | A chave de API não possui o escopo messages:send (resposta inclui required_scope). |
404 | number_not_found | O alias ou o id informado na URL não corresponde a nenhum número cadastrado. |
400 | invalid_number_id | O valor na URL começa com num_, mas não é um id de número válido. |
404 | not_found | A mensagem não foi encontrada, pertence a outra organização ou pertence a um número diferente do da URL (na rota atalho sem {ref}, o número principal): todos esses casos devolvem a mesma resposta, sem distinção. |
404 | request_not_found | A mensagem deixou de ser encontrada enquanto a edição ou exclusão era processada. Consulte a mensagem com GET e, se ela ainda existir, tente de novo. |
409 | number_not_connected | O número que enviou a mensagem não está conectado ao WhatsApp. Reconecte o número e tente de novo. |
500 | internal_error | Erro interno inesperado no servidor. |
Quais mensagens podem ser editadas ou apagadas
A mensagem precisa atender aos critérios abaixo. Se falhar em qualquer
um, a requisição é rejeitada na hora, com 400 message_not_editable /
400 message_not_deletable (ou 400 edit_window_expired, no prazo da edição), antes de qualquer contato com o WhatsApp:
- Enviada por este número, via esta API: a mensagem precisa ter sido
enviada pela API do Turbo Notify por este número (
direction: "outbound"eorigin: "tn"). Dois tipos de mensagem ficam de fora por este critério:- Mensagens recebidas: mensagens que um contato ou participante enviou para você. Elas pertencem ao remetente, não a você, e o WhatsApp não permite que você edite ou revogue uma mensagem que não enviou.
- Mensagens digitadas no próprio celular (
origin: "native"), enviadas fora da API, direto no aparelho. O Turbo Notify não é o cliente que originou essa mensagem, então também não pode editá-la ou revogá-la.
- Tipo de conteúdo (apenas na edição): o WhatsApp só permite editar
mensagens de texto, a legenda de uma imagem, vídeo ou documento e
eventos de calendário, e o corpo da requisição precisa ser do tipo da
mensagem:
textpara um texto,captionpara uma imagem, vídeo ou documento, os campos do evento (name,start_at) para um evento. Editar um áudio, uma figurinha ou outro tipo (localização, cartão de contato, botão de link) falha com400 message_not_editable: para esses, apagar é a única alteração. - Status: apenas as mensagens que já estão em
sent,deliveredouread, e que ainda não foram apagadas, são aceitas. Mensagens aindapending, jáfailed, ou já apagadas retornam400 message_not_editable/400 message_not_deletable. - Prazo (apenas na edição de texto e de legenda): o texto ou a legenda só
pode ser alterado até 15 minutos depois do envio. Depois disso a resposta é
400 edit_window_expired. Os 15 minutos não valem para evento de calendário.
Veja também: Consultar mensagem · Status da mensagem · Webhook.