Listar mensagens
Liste as mensagens (enviadas e recebidas) de um número, paginadas por cursor, mais recentes primeiro. Sem filtros, a lista é a caixa de entrada inteira do número; com group_id, contact_id e direction você lê só um grupo, só uma pessoa ou só o que foi recebido. Cada item é um resumo: para o corpo completo de uma mensagem específica, use Consultar mensagem.
Use GET /v1/messages para o número principal. Para números extras, use GET /v1/numbers/{ref}/messages, onde {ref} é o alias atual do número ou o id dele (num_…, o number_id do envelope do webhook/polling e de cada item). Prefira o id: o alias lido numa mensagem antiga pode ter sido renomeado depois.
Escopo obrigatório: messages:read
Parâmetros de consulta
| Parâmetro | Tipo | Descrição |
|---|---|---|
cursor | string | Token opaco retornado em cursor pela página anterior. Ausente = primeira página. |
limit | int | Quantidade de itens por página. Intervalo: 1 a 200. Padrão: 50. |
group_id | string | Só as mensagens deste grupo (grp_ + 32 hex). O grupo precisa ser do número da URL. |
contact_id | string | Só as mensagens que este contato enviou ou recebeu (ct_ + 32 hex), inclusive o que ele disse em grupos. O contato precisa ser do número da URL. |
direction | string | inbound (só as recebidas) ou outbound (só as enviadas). |
Os filtros se combinam: a lista traz só as mensagens que atendem a todos os que você informou. Sem nenhum filtro, vem tudo. Os ids de grupo e de contato vêm dos grupos e contatos do número e dos eventos (from.contact_id, to.group_id).
Filtrar a lista
Ler um grupo:
curl -G "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages" \ -H "Authorization: Bearer SUA_CHAVE" \ --data-urlencode "group_id=grp_0198d3c5a1b24d3e9f01a2b3c4d5e6f7" \ --data-urlencode "limit=50"Ler o que uma pessoa disse nesse grupo (group_id e contact_id juntos):
curl -G "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages" \ -H "Authorization: Bearer SUA_CHAVE" \ --data-urlencode "group_id=grp_0198d3c5a1b24d3e9f01a2b3c4d5e6f7" \ --data-urlencode "contact_id=ct_0198d3c5a1b24d3e9f01a2b3c4d5e6f8"Ler só o que o número recebeu:
curl -G "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages" \ -H "Authorization: Bearer SUA_CHAVE" \ --data-urlencode "direction=inbound"Para continuar na página seguinte, repita a chamada com os mesmos filtros e o cursor devolvido pela página anterior. Não troque um filtro no meio da leitura: o cursor pertence à lista que o gerou. Para mudar de filtro, comece de novo sem cursor.
Listar mensagens
curl -X GET \ "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages?limit=20" \ -H "Authorization: Bearer SUA_CHAVE"const response = await fetch( 'https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages?limit=20', { method: 'GET', headers: { 'Authorization': 'Bearer SUA_CHAVE' } });
const { data, cursor, has_more } = await response.json();console.log(data);import requests
url = "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages"headers = {"Authorization": "Bearer SUA_CHAVE"}params = {"limit": 20}
response = requests.get(url, headers=headers, params=params)result = response.json()print(result["data"])package main
import ( "fmt" "io" "net/http")
func main() { req, _ := http.NewRequest( "GET", "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages?limit=20", 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$url = 'https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages?limit=20';
$options = [ 'http' => [ 'header' => ['Authorization: Bearer SUA_CHAVE'], 'method' => 'GET' ]];
$context = stream_context_create($options);$result = file_get_contents($url, false, $context);$response = json_decode($result, true);var_dump($response['data']);?>var client = new HttpClient();client.DefaultRequestHeaders.Add("Authorization", "Bearer SUA_CHAVE");
var response = await client.GetAsync( "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages?limit=20");var result = await response.Content.ReadAsStringAsync();Console.WriteLine(result);import java.net.http.*;import java.net.URI;
public class ListarMensagens { public static void main(String[] args) throws Exception { var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder() .uri(URI.create( "https://api.turbonotify.com/v1/numbers/ALIAS_DO_NUMERO/messages?limit=20")) .header("Authorization", "Bearer SUA_CHAVE") .GET() .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); }}Resposta (200)
{
"data": [
{
"id": "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70",
"organization_id": "org_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"number_alias": "main",
"number_id": "num_0f8e2c4a6b1d4e7f9a3c5d7e9b1a3c5e",
"from": {
"contact_id": "ct_0198d3c5a1b24d3e9f01a2b3c4d5e6f8",
"name": "Lucas",
"number": "5511988887777"
},
"to": {
"group_id": "grp_0198d3c5a1b24d3e9f01a2b3c4d5e6f7",
"name": "Equipe de Vendas"
},
"direction": "inbound",
"content_type": "text",
"channel": "whatsapp",
"origin": "native",
"preview": "Pedido 4521 separado",
"content_expired": false,
"at": "2026-06-01T13:04:07Z",
"status": {
"state": "delivered",
"at": "2026-06-01T13:04:09Z",
"error": null
}
},
{
"id": "msg_01993c5a1b24d3e9f01a2b3c4d5e6f71",
"organization_id": "org_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"number_alias": "main",
"number_id": "num_0f8e2c4a6b1d4e7f9a3c5d7e9b1a3c5e",
"from": {
"number": "5511973117056",
"alias": "main"
},
"to": {
"contact_id": "ct_0198d3c5a1b24d3e9f01a2b3c4d5e6f9",
"name": "Ana",
"number": "5511977776666"
},
"direction": "outbound",
"content_type": "text",
"channel": "whatsapp",
"origin": "tn",
"preview": "Olá, seu pedido foi confirmado!",
"content_expired": false,
"at": "2026-06-01T13:00:05Z",
"status": {
"state": "delivered",
"at": "2026-06-01T13:01:00Z",
"error": null
}
},
{
"id": "msg_01993c5a1b24d3e9f01a2b3c4d5e6f72",
"organization_id": "org_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"number_alias": "main",
"number_id": "num_0f8e2c4a6b1d4e7f9a3c5d7e9b1a3c5e",
"from": {
"contact_id": "ct_0198d3c5a1b24d3e9f01a2b3c4d5e6f9",
"name": "Ana",
"number": "5511977776666"
},
"to": {
"number": "5511973117056",
"alias": "main"
},
"direction": "inbound",
"content_type": "image",
"channel": "whatsapp",
"origin": "native",
"preview": "Foto do produto",
"content_expired": false,
"at": "2026-06-01T12:58:00Z",
"status": {
"state": "delivered",
"at": "2026-06-01T12:58:00Z",
"error": null
}
}
],
"cursor": "eyJpZCI6Im1zZ18wMSJ9",
"has_more": true
} Cada item traz o mesmo envelope do evento message.* daquela mensagem (veja Webhooks), mais content_expired e status. As menções, a transcrição e o conteúdo completo (text, mídia) ficam só na resposta de Consultar mensagem.
Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
data | array | Página de mensagens, mais recentes primeiro |
cursor | string|null | Token opaco para a próxima página; null quando não há mais itens |
has_more | boolean | true se há mais páginas disponíveis |
data[].id | string | Identificador público da mensagem (msg_ + 32 hex) |
data[].organization_id | string | Identificador público da sua organização (org_ + 32 hex) |
data[].number_alias | string | O alias que o número tinha na hora da mensagem ("main" ou o alias de um número extra), o mesmo valor do evento. Se o número for renomeado depois, as mensagens antigas mantêm o alias antigo. |
data[].number_id | string | Identificador do número (num_…), logo depois do number_alias. Nunca muda, mesmo que o alias seja renomeado. |
data[].from | object | Remetente. Mensagem recebida: {contact_id?, name?, number}. Mensagem enviada: {contact_id?, name?, number, alias} |
data[].to | object | Destinatário. Em um grupo: {group_id, name?}. Mensagem recebida em conversa individual: {contact_id?, name?, number, alias}. Mensagem enviada em conversa individual: {contact_id?, name?, number} |
data[].direction | string | outbound (enviada) ou inbound (recebida) |
data[].content_type | string | text, image, audio, video, document, sticker, location, calendar, etc. |
data[].channel | string | Sempre whatsapp |
data[].origin | string | tn (enviada pela API) ou native (enviada ou recebida no próprio WhatsApp) |
data[].preview | string | Trecho inicial do conteúdo, sem partir emoji ou acento ao meio (mesmo valor de GET /v1/numbers/{ref}/messages/{id}) |
data[].content_expired | boolean | true quando o conteúdo já passou do prazo de retenção do seu plano e foi apagado. A mensagem continua na lista por 12 meses, com preview vazio |
data[].at | string | ISO 8601: momento estável de ocorrência da mensagem (outbound: envio real; inbound: recebimento). Não avança conforme a mensagem progride sent → delivered → read. |
data[].status | object | {state, at, error}: estado atual de entrega e o timestamp dessa transição. O error vem preenchido ({code, message, hint}) quando state é "failed" e é null nos demais estados, igual nas duas rotas. |
Ordenação e paginação
- Os itens vêm na ordem em que o Turbo Notify aceitou cada mensagem (mais
recente primeiro), não estritamente por
at. Para a maioria das mensagens os dois coincidem; em envios outbound,at(o momento real de envio) pode ficar alguns segundos depois do aceite, porque o envio simula o tempo de digitação antes de chegar ao WhatsApp. Por issoatpode sair levemente fora de ordem dentro da página. A página é sempre completa e sem lacunas. - A paginação usa
cursor: passe o valor retornado comocursorna próxima chamada, com os mesmos filtros, atéhas_moreserfalse. Trate o cursor como um token opaco e não monte nem altere o valor. - Só o conteúdo expira: uma mensagem que já passou do
prazo de retenção de conteúdo do seu plano continua
na lista por 12 meses, com
content_expired: trueepreviewvazio. O remetente e o grupo continuam na mensagem, e ela continua aparecendo nos filtrosgroup_idecontact_id.
Erros possíveis
| Status | Código de erro | Descriçã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:read (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. |
400 | invalid_group_id | O group_id não está no formato grp_ + 32 hex. |
400 | invalid_contact_id | O contact_id não está no formato ct_ + 32 hex. |
400 | invalid_cursor | O cursor não é um valor devolvido por uma página anterior. Use o valor exato da última resposta, com os mesmos filtros, ou recomece sem cursor. |
404 | group_not_found | O grupo não existe neste número. Um group_id de outro número responde assim, em vez de devolver uma lista vazia. |
404 | contact_not_found | O contato não existe neste número. Um contact_id de outro número responde assim, em vez de devolver uma lista vazia. |
422 | validation_error | Um parâmetro de consulta não passou na validação: por exemplo limit fora do intervalo de 1 a 200, ou direction diferente de inbound e outbound. A resposta traz details.errors com os campos rejeitados. |
500 | internal_error | Erro interno inesperado no servidor. |
Veja também: Consultar mensagem · Polling de eventos.