RotaISP Conecta: API pública v1
Envio de mensagens do WhatsApp oficial (templates aprovados pela Meta) a partir do ERP do provedor. Autenticação por chave de API (criada no painel do Conecta, em "API e webhook"), idempotência obrigatória e aviso de status assinado (HMAC-SHA256).
Endereço: https://api.conecta.rotaisp.com.br · Especificação OpenAPI: GET /v1/openapi.json
Autenticação
Authorization: Bearer rck_... (ou o cabeçalho X-Api-Key). Escopos: mensagens:enviar, mensagens:ler. A chave aparece uma vez só, ao ser criada no painel; guarde-a como senha. Chave ausente, inválida, revogada ou de provedor suspenso recebe sempre o mesmo 401.
Idempotência
Todo POST /v1/mensagens leva o cabeçalho Idempotency-Key. Repetir o pedido com a mesma chave e o mesmo corpo devolve a mesma mensagem (com Idempotent-Replayed: true), mesmo que os pedidos cheguem ao mesmo tempo; nada é enviado duas vezes. A mesma chave com outro corpo recebe 409.
A chave vale numa janela de 7 dias, contados do aceite do pedido. Depois disso o Conecta apaga o resumo do pedido, e a mesma chave é tratada como um pedido novo: repetir um pedido antigo com a mesma chave gera outra mensagem. Use uma chave nova para cada mensagem (por exemplo, o número da fatura com a data).
Limite de requisições
Há um limite de requisições por minuto para cada chave. Os cabeçalhos RateLimit-Limit e RateLimit-Remaining mostram o limite e o que resta; ao passar dele, 429 com Retry-After (segundos).
Rotas
POST /v1/mensagens
Pede o envio de uma mensagem por template
Confere o pedido contra o template APROVADO da conta do número e põe a mensagem na fila. Escopo mensagens:enviar. Com a mesma Idempotency-Key e o mesmo corpo, devolve a mesma mensagem (cabeçalho Idempotent-Replayed: true), inclusive em pedidos simultâneos; com outro corpo, 409. A chave vale numa janela de 7 dias contados do aceite: depois disso o Conecta apaga o resumo do pedido, e a mesma chave (com o mesmo corpo ou outro) é tratada como um pedido NOVO, que gera outra mensagem. Validade na fila: uma mensagem que não sair em 24 horas contadas do aceite (conta ou número pausado, novas tentativas) expira: status FALHA com erro.classe EXPIRADO, e o aviso de status é enviado ao webhook. Módulo Envios desligado (pelo administrador do provedor, no painel) ou suspenso (pela RotaISP) com a mensagem ainda na fila: ela falha na hora (em até uns 10 segundos), com status FALHA e erro.classe MODULO_DESLIGADO, e o aviso de status é enviado ao webhook. Ligar o módulo de novo não a reenvia: peça de novo, com outra Idempotency-Key.
Parâmetros
| Nome | Onde | Obrigatório | Descrição |
|---|---|---|---|
Idempotency-Key | header | sim | De 1 a 100 caracteres ASCII visíveis, única por mensagem no provedor dentro da janela de 7 dias. |
Corpo
| Campo | Tipo | Obrigatório | Regra | Descrição |
|---|---|---|---|---|
numero | string | sim | formato ^\d{5,30}$ | O número que envia: o identificador do número na Meta (phone_number_id), mostrado no painel. |
para | string | sim | formato ^\+?\d{10,15}$ | Telefone do cliente com DDI (só dígitos, + opcional). |
template | string | sim | formato ^[a-z0-9_]{1,512}$ | Nome do template aprovado da conta do número. |
idioma | string | sim | Idioma do template (ex.: pt_BR). | |
variaveis | lista de string | não | até 20 itens | Valores de {{1}}, {{2}}... do texto, na ordem. |
cabecalho | string | não | até 60 caracteres | Valor da variável do cabeçalho de texto, se o template tiver. |
documento | object | não | PDF do cabeçalho, por endereço https:// público. | |
documento.url | string | sim | até 2000 caracteres | |
documento.nomeArquivo | string | não | até 240 caracteres | |
imagem | object | não | Imagem do cabeçalho, por endereço https:// público. | |
imagem.url | string | sim | até 2000 caracteres | |
codigo | string | não | até 20 caracteres | O que o botão "Copiar código" copia (até 20 caracteres, regra da Meta). |
links | lista de string | não | até 2 itens | O fim de cada botão de link com variável, na ordem. |
pix | object | não | Detalhes do pedido com o botão "Copiar código Pix" (Pix copia e cola), para template com esse botão. Leva também a referência, a descrição e o valor do pedido. Pode vir junto com boleto: os dois aparecem no mesmo card. | |
pix.codigo | string | sim | até 512 caracteres | Pix copia e cola. |
pix.chave | string | sim | até 77 caracteres | Chave Pix do recebedor (CPF, CNPJ, e-mail, +55... ou aleatória). |
pix.recebedor | string | sim | até 100 caracteres | |
pix.referencia | string | sim | formato ^[A-Za-z0-9_.-]{1,60}$ | Única por mensagem (ex.: o número do boleto, com -2 no reenvio). |
pix.descricao | string | sim | até 60 caracteres | |
pix.valorCentavos | integer | sim | mínimo 1 | Em centavos (9990 = R$ 99,90). |
boleto | object | não | Linha digitável do boleto nos detalhes do pedido (o mesmo card do Pix), para template com o botão "Copiar código Pix". Com pix, mande só linhaDigitavel (os dados do pedido vêm do pix); sem pix, mande também referencia, descricao e valorCentavos. | |
boleto.linhaDigitavel | string | sim | até 60 caracteres | 47 dígitos (boleto bancário) ou 48 (conta de consumo), com ou sem pontos e espaços. |
boleto.referencia | string | não | formato ^[A-Za-z0-9_.-]{1,60}$ | Só sem pix: única por mensagem. |
boleto.descricao | string | não | até 60 caracteres | Só sem pix. |
boleto.valorCentavos | integer | não | mínimo 1 | Só sem pix: em centavos. |
Exemplo
{
"numero": "109876543210987",
"para": "5573999990000",
"template": "cobranca_pix",
"idioma": "pt_BR",
"variaveis": [
"Maria",
"R$ 99,90"
],
"documento": {
"url": "https://erp.provedor.example.com/boletos/48211907.pdf",
"nomeArquivo": "boleto-48211907.pdf"
},
"pix": {
"codigo": "00020101021226850014br.gov.bcb.pix2563pix.example.com/qr/v2/cobv/9d36b84f5204000053039865802BR6304ABCD",
"chave": "12345678000199",
"recebedor": "Provedor Exemplo",
"referencia": "48211907",
"descricao": "Fatura Internet",
"valorCentavos": 9990
},
"boleto": {
"linhaDigitavel": "23793.38128 60000.000003 00000.000400 1 84340000009990"
}
}
Respostas
| Código | Quando | Exemplo |
|---|---|---|
| 202 | Aceita (ou a mesma de antes, com a mesma chave de idempotência). | |
| 400 | Pedido inválido ou que não combina com o template. | O template tem 2 variável(is) no texto; foram enviadas 1 em "variaveis". |
| 401 | Chave ausente, inválida ou revogada (sempre a mesma resposta). | Chave de API inválida. |
| 403 | A chave não tem o escopo mensagens:enviar, ou o módulo Envios está desligado (pelo provedor) ou suspenso (pela RotaISP) para o provedor (codigo: MODULO_DESLIGADO; o texto diz qual dos dois). | Esta chave de API não tem permissão para esta operação. |
| 409 | A chave de idempotência já foi usada com outro corpo nos últimos 7 dias. | Esta Idempotency-Key já foi usada com outro pedido. |
| 429 | Limite de requisições por chave (veja Retry-After). | Limite de requisições atingido. Tente de novo em instantes. |
| 503 | API pública não configurada. | A API pública ainda não foi configurada. Fale com o suporte da RotaISP. |
GET /v1/mensagens/{id}
Situação de uma mensagem
Escopo mensagens:ler. Mensagem de outro provedor, inexistente ou id malformado: o mesmo 404.
Parâmetros
| Nome | Onde | Obrigatório | Descrição |
|---|---|---|---|
id | path | sim |
Respostas
| Código | Quando | Exemplo |
|---|---|---|
| 200 | A mensagem. | |
| 401 | Chave ausente, inválida ou revogada. | Chave de API inválida. |
| 403 | A chave não tem o escopo mensagens:ler, ou o módulo Envios está desligado (pelo provedor) ou suspenso (pela RotaISP) para o provedor (codigo: MODULO_DESLIGADO; o texto diz qual dos dois). | Esta chave de API não tem permissão para esta operação. |
| 404 | Não encontrada. | Mensagem não encontrada. |
| 429 | Limite de requisições por chave. | Limite de requisições atingido. Tente de novo em instantes. |
A mensagem (resposta do POST, do GET e do aviso)
| Campo | Tipo | Obrigatório | Regra | Descrição |
|---|---|---|---|---|
id | string uuid | não | ||
idempotencia | string | não | ||
status | string | não | um de: NA_FILA, ENVIADO, ENTREGUE, LIDO, FALHA | |
wamid | string | não | ||
para | string | não | Telefone do cliente, só dígitos. Depois de 90 dias, mascarado: só os 4 últimos dígitos (ex.: *********0000). | |
template | object | não | ||
template.nome | string | não | ||
template.idioma | string | não | ||
erro | object | não | ||
erro.codigo | integer | não | Código de erro da Meta, quando houver. | |
erro.classe | string | não | Classe do erro: TEMPORARIO, DESTINATARIO, TEMPLATE, CONTA, ESPERADO, INCERTO, OUTRO (da Meta), ARQUIVO (o PDF ou a imagem não veio), DEMONSTRACAO, EXPIRADO (mais de 24 horas na fila sem sair, ou os dados apagados pela retenção) ou MODULO_DESLIGADO (o módulo Envios foi desligado ou suspenso com a mensagem na fila). | |
erro.detalhe | string | não | O motivo, em português. | |
statusEm | string date-time | não | ||
criadoEm | string date-time | não |
Aviso de status (webhook)
Um POST por mudança de status (ENVIADO, ENTREGUE, LIDO ou FALHA). Assinatura: X-Conecta-Assinatura: v1=<hex>, o HMAC-SHA256 com o segredo do webhook sobre <X-Conecta-Timestamp>.<corpo cru>. Recuse assinatura errada e timestamp com mais de 300 s de diferença, e tire duplicata pelo X-Conecta-Evento. Responda 2xx. Sem 2xx (ou sem resposta em 10 s), o aviso é reenviado com espera crescente (1, 5, 15, 60, 180, 720 min), até 7 tentativas; por isso um aviso pode chegar depois de outro mais novo: use a ordem ENVIADO < ENTREGUE < LIDO (FALHA é final).
| Campo | Tipo | Obrigatório | Regra | Descrição |
|---|---|---|---|---|
evento | string | não | um de: mensagem.status | |
id | string uuid | não | ||
criadoEm | string date-time | não | ||
mensagem | Mensagem | não |
Aviso de número pausado ou liberado (webhook)
Quando a Meta indica que UM número não pode enviar mensagens (com a conta liberada), o Conecta pausa só esse número: as mensagens por ele ficam na fila (até 24 horas) e os outros números da conta continuam enviando. Chega um aviso com numero.envios PAUSADO e o motivo. A pausa só sai quando um administrador do provedor clica em "Já corrigi, retomar este número" no painel; aí chega o aviso LIBERADO. Mesmos cabeçalhos, mesma assinatura e mesmas novas tentativas do aviso de status.
| Campo | Tipo | Obrigatório | Regra | Descrição |
|---|---|---|---|---|
evento | string | não | um de: numero.envios | |
id | string uuid | não | ||
criadoEm | string date-time | não | ||
numero | object | não | ||
numero.numero | string | não | O identificador do número na Meta (phone_number_id). | |
numero.envios | string | não | um de: PAUSADO, LIBERADO | |
numero.motivo | string | não | O motivo da Meta, em português (na pausa). | |
numero.em | string date-time | não | Quando o número foi pausado ou liberado. |
Conferir a assinatura (Node.js)
const crypto = require("node:crypto");
// corpoCru: o corpo EXATO recebido (Buffer ou string), antes de qualquer JSON.parse.
function avisoDoConectaValido(corpoCru, cabecalhos, segredo, toleranciaSeg = 300) {
const ts = Number(cabecalhos["x-conecta-timestamp"]);
const recebida = String(cabecalhos["x-conecta-assinatura"] || "");
if (!Number.isInteger(ts) || Math.abs(Date.now() / 1000 - ts) > toleranciaSeg) return false;
const esperada = "v1=" + crypto.createHmac("sha256", segredo).update(ts + ".").update(corpoCru).digest("hex");
return esperada.length === recebida.length && crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(recebida));
}