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

NomeOndeObrigatórioDescrição
Idempotency-KeyheadersimDe 1 a 100 caracteres ASCII visíveis, única por mensagem no provedor dentro da janela de 7 dias.

Corpo

CampoTipoObrigatórioRegraDescrição
numerostringsimformato ^\d{5,30}$O número que envia: o identificador do número na Meta (phone_number_id), mostrado no painel.
parastringsimformato ^\+?\d{10,15}$Telefone do cliente com DDI (só dígitos, + opcional).
templatestringsimformato ^[a-z0-9_]{1,512}$Nome do template aprovado da conta do número.
idiomastringsimIdioma do template (ex.: pt_BR).
variaveislista de stringnãoaté 20 itensValores de {{1}}, {{2}}... do texto, na ordem.
cabecalhostringnãoaté 60 caracteresValor da variável do cabeçalho de texto, se o template tiver.
documentoobjectnãoPDF do cabeçalho, por endereço https:// público.
documento.urlstringsimaté 2000 caracteres
documento.nomeArquivostringnãoaté 240 caracteres
imagemobjectnãoImagem do cabeçalho, por endereço https:// público.
imagem.urlstringsimaté 2000 caracteres
codigostringnãoaté 20 caracteresO que o botão "Copiar código" copia (até 20 caracteres, regra da Meta).
linkslista de stringnãoaté 2 itensO fim de cada botão de link com variável, na ordem.
pixobjectnãoDetalhes 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.codigostringsimaté 512 caracteresPix copia e cola.
pix.chavestringsimaté 77 caracteresChave Pix do recebedor (CPF, CNPJ, e-mail, +55... ou aleatória).
pix.recebedorstringsimaté 100 caracteres
pix.referenciastringsimformato ^[A-Za-z0-9_.-]{1,60}$Única por mensagem (ex.: o número do boleto, com -2 no reenvio).
pix.descricaostringsimaté 60 caracteres
pix.valorCentavosintegersimmínimo 1Em centavos (9990 = R$ 99,90).
boletoobjectnãoLinha 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.linhaDigitavelstringsimaté 60 caracteres47 dígitos (boleto bancário) ou 48 (conta de consumo), com ou sem pontos e espaços.
boleto.referenciastringnãoformato ^[A-Za-z0-9_.-]{1,60}$Só sem pix: única por mensagem.
boleto.descricaostringnãoaté 60 caracteresSó sem pix.
boleto.valorCentavosintegernãomínimo 1Só 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ódigoQuandoExemplo
202Aceita (ou a mesma de antes, com a mesma chave de idempotência).
400Pedido inválido ou que não combina com o template.O template tem 2 variável(is) no texto; foram enviadas 1 em "variaveis".
401Chave ausente, inválida ou revogada (sempre a mesma resposta).Chave de API inválida.
403A 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.
409A chave de idempotência já foi usada com outro corpo nos últimos 7 dias.Esta Idempotency-Key já foi usada com outro pedido.
429Limite de requisições por chave (veja Retry-After).Limite de requisições atingido. Tente de novo em instantes.
503API 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

NomeOndeObrigatórioDescrição
idpathsim

Respostas

CódigoQuandoExemplo
200A mensagem.
401Chave ausente, inválida ou revogada.Chave de API inválida.
403A 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.
404Não encontrada.Mensagem não encontrada.
429Limite 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)

CampoTipoObrigatórioRegraDescrição
idstring uuidnão
idempotenciastringnão
statusstringnãoum de: NA_FILA, ENVIADO, ENTREGUE, LIDO, FALHA
wamidstringnão
parastringnãoTelefone do cliente, só dígitos. Depois de 90 dias, mascarado: só os 4 últimos dígitos (ex.: *********0000).
templateobjectnão
template.nomestringnão
template.idiomastringnão
erroobjectnão
erro.codigointegernãoCódigo de erro da Meta, quando houver.
erro.classestringnãoClasse 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.detalhestringnãoO motivo, em português.
statusEmstring date-timenão
criadoEmstring date-timenã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).

CampoTipoObrigatórioRegraDescrição
eventostringnãoum de: mensagem.status
idstring uuidnão
criadoEmstring date-timenão
mensagemMensagemnã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.

CampoTipoObrigatórioRegraDescrição
eventostringnãoum de: numero.envios
idstring uuidnão
criadoEmstring date-timenão
numeroobjectnão
numero.numerostringnãoO identificador do número na Meta (phone_number_id).
numero.enviosstringnãoum de: PAUSADO, LIBERADO
numero.motivostringnãoO motivo da Meta, em português (na pausa).
numero.emstring date-timenãoQuando 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));
}