Recadu Guia da API

Mensagens

Todo envio é um POST /v1/messages com instance_id, to, type e um campo com o mesmo nome do type.

{
  "instance_id": "ins_...",
  "to": "5537999990000",
  "type": "text",
  "text": {"body": "Olá!"}
}

to é o telefone com DDI e DDD, só dígitos, ou o JID completo de um contato ou grupo (...@s.whatsapp.net, ...@g.us).

Como o envio funciona

  1. A API valida, aplica as políticas e responde 202 com status: queued.
  2. A mensagem sai em seguida. Mensagens para a mesma conversa saem na ordem em que foram aceitas; conversas diferentes não esperam umas pelas outras.
  3. Se a conexão estiver fora, o Recadu tenta de novo por até 15 minutos. Depois disso a mensagem falha com expired.
  4. Cada mensagem sai uma vez só, mesmo se uma conexão cair no meio do envio.

Use Idempotency-Key para repetir um pedido com segurança (veja Primeiros passos).

Tipos

Texto

"type": "text",
"text": {"body": "Seu pedido saiu. Acompanhe: https://exemplo.com/p/4821", "preview_url": true}

Até 65.536 caracteres. A formatação do WhatsApp vale: *negrito*, _itálico_, ~riscado~, ```mono```.

Imagem, vídeo e documento

"type": "image",
"image": {"media": {"url": "https://exemplo.com/cardapio.jpg"}, "caption": "Cardápio de hoje"}
"type": "document",
"document": {"media": {"media_id": "med_...", "filename": "boleto-outubro.pdf"}, "caption": "Seu boleto"}

A mídia vem de um destes:

filename e mimetype são opcionais; servem para dar nome ao documento e corrigir o tipo quando o servidor de origem informa errado. Legenda até 1.024 caracteres.

Áudio e mensagem de voz

"type": "audio",
"audio": {"media": {"url": "https://exemplo.com/aviso.mp3"}, "voice": true}

Com voice: true, chega como mensagem de voz (a bolinha com o microfone). O Recadu converte para o formato do WhatsApp (OGG/Opus) qualquer áudio ou vídeo (MP3, M4A, WAV...) e informa a duração. Sem voice, chega como arquivo de áudio.

Figurinha

"type": "sticker",
"sticker": {"media": {"url": "https://exemplo.com/figurinha.webp"}}

WebP, 512×512.

Localização

"type": "location",
"location": {"latitude": -19.9167, "longitude": -43.9345, "name": "Loja Centro", "address": "Av. Afonso Pena, 1000"}

Contato

"type": "contact",
"contact": {"name": "Atendimento", "phones": ["+5537999990000"], "organization": "Loja Centro"}

Até 5 telefones.

Botões, listas e enquetes

Em Botões, listas e enquetes.

Citação e menções

Qualquer tipo aceita:

Arquivos

curl -X POST https://api.recadu.com.br/v1/media \
  -H "Authorization: Bearer $GW_API_KEY" \
  -F file=@boleto-outubro.pdf
{
  "id": "med_...",
  "mimetype": "application/pdf",
  "size": 48213,
  "sha256": "9f2c...",
  "filename": "boleto-outubro.pdf",
  "expires_at": "2026-11-12T14:00:00Z",
  "url": "https://midia.recadu.com.br/..."
}

Até 64 MB por arquivo. O arquivo vale por 30 dias; depois, usar o media_id dá 422 media-not-found e GET /v1/media/{id} dá 410 media-expired. GET /v1/media/{id} também devolve um link temporário novo para baixar.

Mídia recebida (foto, áudio, PDF que o contato mandou) chega no webhook com um media_id e o mesmo prazo: baixe e guarde no seu sistema se precisar dela depois.

Status

curl https://api.recadu.com.br/v1/messages/msg_... -H "Authorization: Bearer $GW_API_KEY"
status Significa
queued Aceita, esperando a vez (ordem da conversa, limite por minuto, janela de horário, conexão fora)
sending Saindo agora
sent O WhatsApp aceitou; wa_message_id preenchido
delivered Chegou ao celular do contato
read O contato abriu (só se ele não desligou a confirmação de leitura)
failed Não saiu; veja error.code

Outros campos úteis: scheduled_for (mensagem agendada para a abertura da janela de horário), rendered_as (como um interativo saiu de fato: native_flow_buttons, native_flow_list, legacy_buttons, legacy_list, text_menu), sent_at, delivered_at, read_at, failed_at.

error.code Causa
recipient_not_on_whatsapp O número não tem WhatsApp
media_unavailable Não deu para baixar ou converter a mídia
not_connected, not_paired A instância estava fora
opted_out O contato pediu para sair (SAIR) depois que a mensagem foi aceita
canceled Agendamento cancelado (DELETE)
expired 15 minutos tentando sem sucesso
wa_error, timeout, internal Erro do WhatsApp ou do Recadu: tente de novo; se repetir, fale com o suporte

Para acompanhar sem consultar, use os eventos message.sent, message.status_updated e message.failed (webhooks).

GET /v1/messages lista com filtros instance_id, status, to e since. O texto das mensagens não fica guardado na consulta: só os metadados.

Editar, apagar e reagir

# editar o texto (só mensagens de texto, até 15 minutos depois do envio)
curl -X PATCH https://api.recadu.com.br/v1/messages/msg_... \
  -H "Authorization: Bearer $GW_API_KEY" -H "Content-Type: application/json" \
  -d '{"text": "Seu pedido saiu às 14h10."}'

# apagar para todos (ou cancelar, se ainda estiver agendada)
curl -X DELETE https://api.recadu.com.br/v1/messages/msg_... -H "Authorization: Bearer $GW_API_KEY"

# reagir (emoji vazio remove a reação)
curl -X POST https://api.recadu.com.br/v1/messages/msg_.../reactions \
  -H "Authorization: Bearer $GW_API_KEY" -H "Content-Type: application/json" \
  -d '{"emoji": "👍"}'
Erro Quando
409 message-not-sent A mensagem ainda não saiu
409 not-editable Não é texto
409 edit-window-closed Passaram os 15 minutos
409 message-sending Cancelar uma que já está saindo

"Digitando"

A política da instância já mostra "digitando..." antes de cada texto, por um tempo proporcional ao tamanho (veja políticas). Para controlar à mão:

curl -X POST https://api.recadu.com.br/v1/instances/ins_.../chats/5537999990000/presence \
  -H "Authorization: Bearer $GW_API_KEY" -H "Content-Type: application/json" \
  -d '{"state": "typing"}'