SalesBoost API v1
Comece por aqui Leads CRM WhatsApp Ir para o app ↗

Comece por aqui

Leads, contatos e oportunidades — criar, atualizar e movimentar no funil, direto do seu sistema.

📍 Qual módulo integrar

O SalesBoost tem dois funis. Leads é a tela do dia a dia do lojista (Clientes e Leads), com o funil que ele mesmo edita e que aparece no kanban do Chat WhatsApp. CRM é o pipeline de oportunidades, com valor e previsão de fechamento. Na dúvida, integre com leads.

URL base

https://app.salesboost.com.br/api/v1

Só HTTPS. Uma chamada em HTTP é recusada com 400 insecure_transport — e a chave enviada assim deve ser considerada comprometida e trocada, porque trafegou em claro.

Em trinta segundos

export SB_TOKEN="sb_live_..."

# 1. de qual conta é esta chave?
curl -s https://app.salesboost.com.br/api/v1/me -H "Authorization: Bearer $SB_TOKEN"

# 2. quais são os estágios do funil?
curl -s https://app.salesboost.com.br/api/v1/lead-stages -H "Authorization: Bearer $SB_TOKEN"

# 3. cria um lead
curl -s -X POST https://app.salesboost.com.br/api/v1/leads \
  -H "Authorization: Bearer $SB_TOKEN" -H "Content-Type: application/json" \
  -d '{"nome":"Ana","email":"[email protected]","telefone":"+5541996770938"}'

Autenticação

Chave estática no cabeçalho Authorization, formato Bearer:

Authorization: Bearer sb_live_a1b2c3d4e5f60718_9f8e...<48 hex>

⚠ A chave nunca vai na URL

?token=, ?api_key= e ?access_token= são recusados com 401 token_in_url. URL fica gravada em log de servidor, histórico de proxy e cabeçalho Referer — uma chave que passou por lá já vazou. Se acontecer, revogue e emita outra.

O lojista emite a chave no painel dele, em API & Tokens. A emissão pede duas confirmações: estar logado como dono da conta e digitar um código enviado por SMS para o telefone cadastrado — uma sessão aberta sozinha não cria credencial. Na emissão ele escolhe os escopos:

EscopoPermite
leads:readListar e ler leads e os estágios do funil
leads:writeCriar, alterar e mover leads no funil
contacts:readListar e ler contatos do CRM
contacts:writeCriar e alterar contatos do CRM
deals:readListar e ler oportunidades do CRM e o pipeline
deals:writeCriar, alterar e mover oportunidades do CRM
email:sendEnviar e-mail em nome da empresa
sms:sendEnviar SMS em nome da empresa
whatsapp:readListar instâncias, números e mensagens (templates) da API Oficial
whatsapp:templatesCriar, editar e excluir mensagens (templates) da API Oficial
whatsapp:sendEnviar mensagens de WhatsApp

Faltou escopo, a resposta é 403 insufficient_scope dizendo qual falta. A chave pode ainda ter lista de IPs autorizados e validade — peça as duas: é o que limita o estrago se o token vazar.

Workspace / tenant

O workspace está embutido na chave. Não existe parâmetro de empresa, conta ou tenant em endpoint nenhum, nem na URL nem no corpo — toda chamada opera sobre a empresa dona da chave, e só sobre ela. Uma integração que atende vários clientes SalesBoost usa uma chave por cliente.

GEThttps://app.salesboost.com.br/api/v1/me

Diz de qual workspace é a chave que você tem em mãos, e o que ela pode fazer.

{
  "workspace": { "id": 42, "nome": "Loja da Ana" },
  "key": { "id": 7, "nome": "App do parceiro", "scopes": ["leads:read","leads:write"] },
  "rate_limit": { "per_minute": 120 }
}

Erros

Sempre JSON, no mesmo formato. O request_id também vai no cabeçalho X-Request-Id — mande ele ao pedir suporte, é por onde achamos a chamada no log.

{
  "error": {
    "code": "validation_error",
    "message": "stage inexistente neste workspace. Veja GET /lead-stages.",
    "request_id": "9f2c14a7bd3e5108"
  }
}
HTTPcodeQuando
400insecure_transportChamada sem HTTPS
400invalid_bodyJSON inválido ou acima de 256 KB
401unauthorizedChave ausente, inválida, revogada, expirada ou fora dos IPs
401token_in_urlChave mandada na query string
403insufficient_scopeA chave não tem o escopo necessário
404not_foundRecurso inexistente neste workspace
409idempotency_conflictMesma Idempotency-Key com outro corpo
422validation_errorCampo faltando ou inválido
422use_move_endpointTentou trocar de estágio pelo PATCH
429rate_limitedPassou do limite por minuto
429too_many_attemptsErros de autenticação demais deste IP

Por que o 401 é sempre igual: a mensagem não diz se a chave não existe, se o segredo está errado ou se foi revogada. Distinguir ajudaria mais quem está tentando adivinhar do que quem está integrando. O motivo real fica no nosso log, ligado ao request_id.

Limites e paginação

120 chamadas por minuto, por chave. Toda resposta traz:

RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 34

No 429 vem também Retry-After, em segundos — respeite-o, repetir antes só queima cota. Listagens usam page (1+) e per_page (padrão 25, teto 100), e devolvem meta com o total.

Idempotência

Em POST, mande Idempotency-Key com um valor único por operação (um UUID serve). Se a conexão cair e você repetir, a resposta original volta igual, com Idempotent-Replay: true — sem criar um segundo registro. A chave vale 24 horas.

Idempotency-Key: 5f1c2a0e-8e3b-4d21-9f77-0f2b1a44c901

Reusar a mesma chave com um corpo diferente devolve 409, em vez de esconder o engano.

Leads

✓ É aqui que o lojista trabalha

A tela Clientes e Leads. O funil é o que o próprio lojista edita, e é o mesmo do kanban do Chat WhatsApp — um lead criado aqui aparece nas duas telas.

GEThttps://app.salesboost.com.br/api/v1/lead-stages

Os estágios do funil. A chave é texto (novo, proposta, ou o que o lojista tiver criado) — não é id numérico. É esse valor que vai em stage.

curl -s https://app.salesboost.com.br/api/v1/lead-stages \
  -H "Authorization: Bearer $SB_TOKEN"
{
  "data": [
    { "key": "novo",              "nome": "Novo Lead",         "cor": "#64748b", "ordem": 1 },
    { "key": "atendimento",       "nome": "Em atendimento",    "cor": "#f59e0b", "ordem": 2 },
    { "key": "orcamento_enviado", "nome": "Orçamento enviado", "cor": "#8b5cf6", "ordem": 3 },
    { "key": "fechado",           "nome": "Fechado",           "cor": "#22c55e", "ordem": 4 }
  ]
}

Leia esta lista em vez de fixar valores no código: o lojista cria e renomeia estágios quando quer, e um stage que não existe volta 422.

GEThttps://app.salesboost.com.br/api/v1/leads

ParâmetroDescrição
qBusca por nome ou e-mail
emailE-mail exato
phoneTelefone (compara pelos últimos 8 dígitos)
stageChave do estágio
updated_sinceTimestamp — para sincronizar só o que mudou
page, per_pagePaginação (teto 100)

POSThttps://app.salesboost.com.br/api/v1/leads

Cria — ou atualiza o que já existe. A regra de duplicidade é a mesma da tela: mesmo e-mail OU mesmo telefone é o mesmo lead (telefone comparado pelos últimos 8 dígitos, porque DDI e nono dígito variam conforme quem cadastrou). Achando, devolve "matched": true com HTTP 200; criando, 201.

ℹ No lead que já existe, só preenchemos campo vazio

O que o lojista digitou na tela vale mais que o que vem de fora. stage e extras são a exceção — esses você manda para atualizar mesmo.

curl -s -X POST https://app.salesboost.com.br/api/v1/leads \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5f1c2a0e-8e3b-4d21-9f77-0f2b1a44c901" \
  -d '{"nome":"Ana Souza","email":"[email protected]","telefone":"+55 41 99677-0938",
       "stage":"atendimento",
       "extras":{"pedido":"A-1234","vendedor":"Marcos"}}'
{
  "data": {
    "id": 8812, "nome": "Ana Souza", "email": "[email protected]",
    "telefone": "+55 41 99677-0938", "whatsapp": null,
    "stage": "atendimento", "source": "api", "tags": [],
    "extras": { "pedido": "A-1234", "vendedor": "Marcos" },
    "created_at": 1757630000, "updated_at": 1757630000
  },
  "matched": false
}

Sem stage, o lead nasce no primeiro estágio do funil. A origem fica marcada como api, para o lojista distinguir o que veio da integração do que entrou por popup ou checkout.

Campos livres (extras)

Um objeto de um nível só (texto ou número), até 50 chaves, guardado junto do lead e mostrado na ficha dele. São mesclados, nunca substituídos — o que o popup ou o checkout já gravaram ali não some porque você mandou outra coisa.

Tags (tags)

POST /leads e PATCH /leads/{id} também aceitam "tags": ["a","b"]. As tags são somadas às que o lead já tem — nunca substituídas. Para tirar, use DELETE /leads/{id}/tags. Aplicar por aqui tem o mesmo efeito do endpoint de tags (dispara as automações "Ganhou uma tag").

GEThttps://app.salesboost.com.br/api/v1/leads/{id}

PATCHhttps://app.salesboost.com.br/api/v1/leads/{id}

Altera nome, email, telefone, whatsapp e extras. Não aceita stage — mover é o endpoint abaixo, e dois caminhos para a mesma coisa é como um deles acaba sem a regra.

POSThttps://app.salesboost.com.br/api/v1/leads/{id}/move

Move o lead no funil. O corpo leva a chave do estágio, não um id.

curl -s -X POST https://app.salesboost.com.br/api/v1/leads/8812/move \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"stage":"orcamento_enviado"}'

O movimento é espelhado no kanban do Chat WhatsApp e registrado na linha do tempo do lead — a mesma coisa que acontece quando alguém arrasta o card na tela. Mover para o estágio em que o lead já está devolve 200 com "unchanged": true, em vez de erro.

POSThttps://app.salesboost.com.br/api/v1/leads/{id}/tags

Aplica tags ao lead. É o jeito de o seu sistema iniciar uma automação no SalesBoost: toda automação com o gatilho Ganhou uma tag cujo nome de tag bata com a aplicada (sem diferenciar maiúsculas; nome vazio = qualquer tag) começa para este lead.

curl -s -X POST https://app.salesboost.com.br/api/v1/leads/8812/tags \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tags":["analise_visualizada"]}'
{
  "data": { "id": 8812, "nome": "Ana Souza", "tags": ["cliente", "analise_visualizada"], "...": "..." },
  "added": ["analise_visualizada"],
  "removed": []
}

Escopo: leads:write. Também dispara o webhook lead.tag_added.

DELETEhttps://app.salesboost.com.br/api/v1/leads/{id}/tags

Remove tags — corpo {"tags":["a"]} ou ?tags=a,b na URL. Dispara o gatilho Perdeu uma tag e o webhook lead.tag_removed. Devolve o mesmo formato do POST.

POSThttps://app.salesboost.com.br/api/v1/leads/{id}/unsubscribe

Descadastra o contato: nenhuma automação nova começa para ele e as que estão em andamento param (o próximo passo não sai). Dispara o webhook lead.unsubscribed uma vez só — repetir devolve "already": true.

ℹ O contato também pode se descadastrar pelo WhatsApp

Se ele responder só SAIR, PARAR, STOP, DESCADASTRAR ou NÃO QUERO MAIS (a mensagem inteira, sem diferenciar maiúsculas e acentos), o efeito é o mesmo, e o webhook chega com "source": "whatsapp_palavra". Uma frase que só contém essas palavras ("posso sair mais cedo?") não descadastra ninguém.

POSThttps://app.salesboost.com.br/api/v1/leads/unsubscribe

O mesmo descadastro, sem precisar do id. Corpo: {"telefone":"5541999998888"} — aceita também "email" e um "motivo" opcional que fica no histórico do lead.

⚠ Aplicar uma tag não descadastra ninguém

Uma tag como optout serve para você filtrar no painel, mas não impede envio: a supressão é o que as automações consultam. Para parar de enviar de verdade, use este endpoint — se quiser os dois, mande a tag por POST /leads/{id}/tags e o descadastro por aqui.

CRM

Pipeline de oportunidades, com valor e previsão de fechamento. Funil próprio, separado do de leads: um estágio criado lá não existe aqui.

GEThttps://app.salesboost.com.br/api/v1/pipelines

Os funis da empresa com seus estágios. É daqui que saem os stage_id.

{
  "data": [
    { "id": 3, "nome": "Vendas", "is_default": true,
      "stages": [
        { "id": 11, "nome": "Novo lead",  "tipo": "normal", "ordem": 1 },
        { "id": 14, "nome": "Ganho",      "tipo": "won",    "ordem": 5 },
        { "id": 15, "nome": "Perdido",    "tipo": "lost",   "ordem": 6 }
      ] }
  ]
}

GEThttps://app.salesboost.com.br/api/v1/contacts

Filtros: q, email, phone, updated_since, page, per_page.

POSThttps://app.salesboost.com.br/api/v1/contacts

Cria — ou devolve o que já existe, casando por e-mail e depois pelos últimos 8 dígitos do telefone. Achando, "matched": true e HTTP 200; criando, 201. Mande "match": false para forçar a criação.

curl -s -X POST https://app.salesboost.com.br/api/v1/contacts \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"nome":"Ana Souza","email":"[email protected]","telefone":"+55 41 99677-0938"}'

GEThttps://app.salesboost.com.br/api/v1/contacts/{id}

PATCHhttps://app.salesboost.com.br/api/v1/contacts/{id}

Altera só os campos enviados: nome, email, telefone.

GEThttps://app.salesboost.com.br/api/v1/deals

Filtros: stage_id, pipeline_id, contact_id, status (open, won, lost), updated_since.

POSThttps://app.salesboost.com.br/api/v1/deals

contact_id é obrigatório. pipeline_id e stage_id são opcionais: sem eles, a oportunidade nasce no pipeline padrão da empresa, no primeiro estágio normal.

curl -s -X POST https://app.salesboost.com.br/api/v1/deals \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"contact_id":8812,"titulo":"Orçamento site","valor":2500.00,
       "expected_close":"2026-10-15"}'
{
  "data": { "id": 341, "contact_id": 8812, "pipeline_id": 3, "stage_id": 11,
            "titulo": "Orçamento site", "valor": 2500, "status": "open",
            "position": 4, "expected_close": 1792108800, "closed_at": null,
            "created_at": 1757630400, "updated_at": 1757630400 }
}

GEThttps://app.salesboost.com.br/api/v1/deals/{id}

PATCHhttps://app.salesboost.com.br/api/v1/deals/{id}

Altera titulo, valor e expected_close. Não aceita stage_id — mover é o endpoint abaixo.

POSThttps://app.salesboost.com.br/api/v1/deals/{id}/move

Move a oportunidade de estágio. position é opcional (ordem dentro da coluna).

curl -s -X POST https://app.salesboost.com.br/api/v1/deals/341/move \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"stage_id":14}'

O tipo do estágio manda no resto: cair num won ou lost fecha o negócio e carimba closed_at; voltar para um normal reabre. É a mesma regra de quando alguém arrasta o card na tela — de propósito, senão os relatórios de ganhos passariam a divergir conforme a origem do movimento. O estágio precisa pertencer ao pipeline daquela oportunidade; caso contrário, 422.

Envio de e-mail e SMS

Dois canais transacionais em nome da empresa da chave. Servem para confirmação de pedido, código de acesso, aviso de entrega — mensagem disparada por um evento do seu sistema.

Antes do primeiro e-mail, a empresa precisa ter um remetente verificado. Em Configurações → Email ela cadastra o endereço, confirma a verificação na AWS, e a partir daí o envio funciona. Sem isso a resposta é 503 com sender_not_configured — ou sender_pending_verification, se o endereço foi cadastrado mas ainda não confirmado.

O remetente não é escolhido no corpo. Não existe campo from: se existisse, uma chave vazada poderia mandar mensagem com o domínio de outro cliente. E o e-mail nunca sai com o endereço do SalesBoost — quem recebe é cliente da sua loja e precisa ver a sua marca no remetente.

O SMS é diferente: não tem remetente por empresa. Sai pela conta da plataforma, como qualquer SMS transacional, e o cliente final vê um número curto ou o identificador padrão da operadora.

Os dois respondem 202, e não 200: o provedor aceitou a mensagem. A entrega na caixa ou no aparelho acontece depois e não é garantida nessa resposta.

Há um teto diário por empresa em cada canal, separado do limite por minuto. Estourado, a resposta é 429 com send_quota_exceeded e recomeça à meia-noite. Consulte antes com GET /messages/quota se for disparar um lote.

Use Idempotency-Key nos dois. Sem ela, um timeout de rede seguido de retry manda a mensagem duas vezes — no SMS isso é cobrado duas vezes.

POSThttps://app.salesboost.com.br/api/v1/messages/email

Campos: to e subject obrigatórios, html com o corpo. Opcionais: text (versão sem formatação), reply_to e ref (sua referência, volta no relatório).

curl -s -X POST https://app.salesboost.com.br/api/v1/messages/email \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Idempotency-Key: pedido-8821-confirmacao" \
  -H "Content-Type: application/json" \
  -d '{
        "to": "[email protected]",
        "subject": "Seu pedido #8821 foi confirmado",
        "html": "<p>Oi! Recebemos seu pedido.</p>",
        "ref": "pedido-8821"
      }'
{"data":{"channel":"email","to":"[email protected]","from":"[email protected]","sent":true}}

422 quando o problema é seu (destinatário inválido, assunto vazio) e 503 quando é nosso (transporte fora, remetente não configurado na conta). A distinção existe para você saber se vale repetir a mesma chamada.

POSThttps://app.salesboost.com.br/api/v1/messages/sms

Campos: to (com DDD; sem DDI assume Brasil) e text. Opcionais: type (Transactional, padrão, ou Promotional) e ref.

curl -s -X POST https://app.salesboost.com.br/api/v1/messages/sms \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Idempotency-Key: pedido-8821-sms" \
  -H "Content-Type: application/json" \
  -d '{"to":"11999998888","text":"Seu pedido #8821 saiu para entrega."}'
{"data":{"channel":"sms","to":"+5511999998888","message_id":"abc-123","sent":true}}

Máximo de 1600 caracteres. Vale lembrar que a operadora cobra por trecho de 160 caracteres (70 se houver acento ou emoji): uma mensagem de 400 caracteres é cobrada como três.

GEThttps://app.salesboost.com.br/api/v1/messages/quota

Quanto ainda dá para mandar hoje. Só aparecem os canais que a sua chave pode usar.

{"data":{"email":{"limite":2000,"usado":143,"restante":1857,"excedeu":false},
          "sms":{"limite":200,"usado":200,"restante":0,"excedeu":true}}}

restante igual a -1 significa que aquele canal está sem teto.

WhatsApp API Oficial (Meta)

Endpoints exclusivos de quem usa a API Oficial no SalesBoost. Não existem para o SalesBoost Connect: template é um conceito da Meta, e só o número oficial tem.

Por que template existe. Passadas 24 horas da última mensagem do cliente, a Meta só entrega mensagem em formato aprovado por ela — o template. É por isso que "seu pedido foi enviado" precisa ser cadastrado e revisado antes de poder sair.

O template vive na Meta, não aqui. A cópia local existe para listar rápido e para o histórico. Criar, editar e excluir falam com a Meta na hora, e o template volta como PENDING — quem aprova é ela, em minutos ou horas. Por isso essas chamadas respondem 202: o registro existe, mas ainda não serve para enviar.

O status chega sozinho. Quando a Meta termina a revisão, ela avisa o SalesBoost pelo webhook e a cópia local é atualizada. Para isso o lojista precisa ter assinado o campo message_template_status_update no app dele na Meta — o painel mostra essa instrução na tela de configuração do WhatsApp. Sem o webhook, use POST /meta/templates/sync.

🔑 Escopos

Ler e sincronizar pede whatsapp:read. Criar, editar e excluir pedem whatsapp:templates. Enviar pede whatsapp:send. São separados de propósito: uma chave feita para disparar mensagem não precisa poder apagar o template que as automações usam.

Todos os endpoints aceitam instance_id — na query nos GET, no corpo nos POST. Ele só escolhe entre vários números da mesma empresa; a empresa continua saindo da chave. Omitido, vale o primeiro número oficial da conta.

GEThttps://app.salesboost.com.br/api/v1/meta/numbers

Os números oficiais da conta. Serve para descobrir o instance_id e para monitorar o que decide entrega: quality_rating e messaging_limit. Qualidade em RED é aviso de que a Meta vai reduzir o limite do número.

{"data":[{"instance_id":67,"name":"Airsoft Company","phone":"+55 41 9290-9792",
          "phone_number_id":"1146...","waba_id":"1297...","quality_rating":"GREEN",
          "messaging_limit":"TIER_1K","connected":true,"last_connection":"2026-06-24 00:49:12"}]}

GEThttps://app.salesboost.com.br/api/v1/meta/templates

Lista a cópia local — não chama a Meta, então paginar um relatório não gasta chamada externa. Filtros: status, category, language e q (parte do nome). Com with=stats vem também a contagem de envios que este sistema registrou.

curl -s "https://app.salesboost.com.br/api/v1/meta/templates?status=APPROVED&with=stats" \
  -H "Authorization: Bearer $SB_TOKEN"
{"data":[{"id":12,"meta_id":"1297017162489093","name":"pedido_enviado","language":"pt_BR",
          "category":"UTILITY","status":"APPROVED","rejected_reason":null,"quality_score":"GREEN",
          "variables":2,"header":{"format":"TEXT","text":"Pedido {{1}}"},
          "body":"Olá {{1}}, seu pedido {{2}} saiu para entrega.","footer":"Responda se precisar",
          "buttons":[{"type":"URL","text":"Acompanhar","url":"https://…/{{1}}","phone":null}],
          "synced_at":"2026-09-21 09:12:00","created_at":"2026-06-23 18:38:59",
          "stats":{"enviadas":420,"entregues":404,"lidas":288,"falhas":16}}],
 "meta":{"page":1,"per_page":25,"total":3}}

status é o da Meta: APPROVED, PENDING, REJECTED, PAUSED, DISABLED, IN_APPEAL. Só APPROVED pode ser enviado. Em REJECTED, rejected_reason diz o que a Meta reprovou.

GEThttps://app.salesboost.com.br/api/v1/meta/templates/{id}

Um template, com a contagem local sempre incluída. O {id} é o do SalesBoost, não o da Meta.

POSThttps://app.salesboost.com.br/api/v1/meta/templates

Cria e manda para revisão. name aceita só minúsculas, números e _, e não muda depois — nem ele nem language. category é UTILITY (pedido, entrega, cobrança) ou MARKETING (promoção, carrinho). Autenticação tem formato próprio e se cria no Gerenciador da Meta.

Toda variável precisa de exemplo. A Meta recusa o template sem isso, e é a recusa mais comum de quem integra na mão. As variáveis também têm de ser {{1}}, {{2}}… em sequência, sem pular número.

curl -s -X POST https://app.salesboost.com.br/api/v1/meta/templates \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Idempotency-Key: tpl-pedido-enviado-v1" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "pedido_enviado",
        "language": "pt_BR",
        "category": "UTILITY",
        "header_format": "TEXT",
        "header_text": "Pedido {{1}}",
        "header_example": "8821",
        "body_text": "Olá {{1}}, seu pedido {{2}} saiu para entrega.",
        "body_examples": ["Maria", "#8821"],
        "footer_text": "Responda se precisar de ajuda",
        "buttons": [
          { "type": "URL", "text": "Acompanhar",
            "url": "https://sualoja.com.br/p/{{1}}", "url_example": "https://sualoja.com.br/p/8821" },
          { "type": "QUICK_REPLY", "text": "Falar com alguém" }
        ]
      }'

Limites da Meta, validados aqui antes de gastar a chamada: cabeçalho e rodapé com 60 caracteres, corpo com 1024, botão com 25; no máximo 10 botões, sendo até 2 de link e 1 de telefone; e só uma variável no cabeçalho. Cabeçalho de mídia (IMAGE/VIDEO/DOCUMENT) exige um arquivo de exemplo hospedado na Meta — isso hoje só pelo painel, que faz o upload e gera o header_handle.

Use Idempotency-Key: o nome é único por idioma, e um retry às cegas leva 409 em vez de criar duas vezes.

PATCHhttps://app.salesboost.com.br/api/v1/meta/templates/{id}

Mesmo corpo do POST, sem name e language. A Meta só aceita editar template APPROVED, REJECTED ou PAUSED — em análise ela recusa, e a resposta aqui é 409.

Editar devolve o template para revisão. A versão aprovada continua valendo nos envios até a Meta responder sobre a nova, então editar não derruba o disparo que já está no ar.

DELETEhttps://app.salesboost.com.br/api/v1/meta/templates/{id}

Apaga na Meta e aqui, só naquele idioma. Automação e integração que usam esse template param de enviar na hora — não há lixeira do lado da Meta.

{"data":{"id":12,"name":"pedido_enviado","deleted":true}}

POSThttps://app.salesboost.com.br/api/v1/meta/templates/sync

Repuxa a lista da Meta e atualiza status, motivo de recusa e nota de qualidade. Template apagado direto no Gerenciador da Meta também sai daqui. Normalmente o webhook já mantém isso em dia; use no primeiro uso ou quando o webhook não estiver assinado.

{"data":{"synced":14,"removed":1}}

GEThttps://app.salesboost.com.br/api/v1/meta/template-metrics

Métricas de um template: template_id obrigatório (o id do SalesBoost) e days opcional, de 1 a 90 (padrão 30).

meta são os números oficiais e contam todo envio do número, inclusive o que saiu por automação ou por outra integração. local é só o que este sistema registrou, e serve quando a Meta não devolve as métricas — o que acontece enquanto a coleta está desligada no WABA (a primeira chamada tenta ligar sozinha).

{"data":{"template":{"id":12,"name":"pedido_enviado","language":"pt_BR","status":"APPROVED"},
         "dias":30,
         "meta":{"disponivel":true,"erro":null,"enviadas":420,"entregues":404,"lidas":288,
                 "cliques":97,"dias":[{"data":"2026-09-01","enviadas":40,"entregues":38,
                 "lidas":25,"cliques":9}]},
         "local":{"enviadas":400,"entregues":380,"lidas":270,"falhas":12}}}

POSThttps://app.salesboost.com.br/api/v1/meta/messages

Envia um template aprovado. to em formato internacional, template com o nome, e variables na ordem do corpo ({{1}}, {{2}}…). Faltando variável, a resposta é 422 dizendo quantas o template espera — antes de gastar a chamada na Meta.

curl -s -X POST https://app.salesboost.com.br/api/v1/meta/messages \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Idempotency-Key: pedido-8821-whatsapp" \
  -H "Content-Type: application/json" \
  -d '{
        "to": "5541999998888",
        "template": "pedido_enviado",
        "variables": ["Maria", "#8821"],
        "header_variable": "8821",
        "button_variables": ["8821"]
      }'
{"data":{"message_id":"wamid.HBgN…","to":"5541999998888","template":"pedido_enviado",
         "language":"pt_BR","sent":true}}

Opcionais: header_variable (cabeçalho de texto), header_media_url (cabeçalho de mídia — link direto do arquivo) e button_variables (o sufixo do botão de link).

202 quer dizer que a Meta aceitou. A entrega no aparelho acontece depois e chega pelo webhook, não nessa resposta. A mensagem fica registrada no Chat WhatsApp do lojista, como qualquer envio pelo painel, e entra na contagem de local das métricas.

Use Idempotency-Key. A Meta cobra por conversa: um timeout seguido de retry manda a mensagem duas vezes e é cobrado duas vezes.

Erros deste bloco

CódigoHTTPO que fazer
whatsapp_not_configured409A conta não tem número oficial configurado, ou falta o WABA ID nas credenciais
invalid_template422Alguma regra da Meta não fecha; a mensagem diz qual
template_conflict409Nome repetido no idioma, ou status que não permite a operação
meta_rejected422A Meta recusou; a mensagem dela vem junto
meta_unavailable503Problema do lado da Meta — vale repetir a mesma chamada

Webhooks

O SalesBoost avisa o seu sistema quando algo acontece: o contato respondeu, leu a mensagem, pediu descadastro, o atendimento foi encerrado, uma tag entrou ou saiu. Você cadastra uma URL HTTPS e escolhe os eventos; cada ocorrência vira um POST JSON assinado.

Eventos

EventoQuandodata
message.receivedO contato mandou mensagem (respondeu) message_id, channel, phone, name, text, type, lead_id, received_at
message.deliveredMensagem enviada chegou ao aparelho message_id, channel, status, phone, lead_id, at
message.readMensagem enviada foi lida (visualizada); áudio ouvido conta como lida
message.failedMensagem enviada falhou
conversation.closedAtendimento encerrado no Chat WhatsApp phone, name, lead_id, stage, closed_by, closed_at
lead.unsubscribedContato pediu descadastro (pela API ou por "SAIR" no WhatsApp) lead_id, phone, email, source, text, at
lead.tag_addedTag aplicada ao leadlead, tags, source
lead.tag_removedTag removida do lead

channel é whatsapp_oficial (API da Meta) ou whatsapp_connect. phone vem só com dígitos, com DDI (5541999998888). lead_id é o lead com esse telefone, ou null se não houver. Os status de mensagem valem para qualquer mensagem enviada pelo número — da automação, da IA ou de um atendente.

Todo POST tem este formato:

POST https://seu-sistema.com/salesboost/webhook
Content-Type: application/json
User-Agent: SalesBoost-Webhooks/1.0
X-SalesBoost-Event: message.received
X-SalesBoost-Delivery: evt_4f0c9a2b7d1e8c3a5b6f7e21
X-SalesBoost-Signature: t=1759160000,v1=5d41402abc4b2a76b9719d911017c592...

{
  "id": "evt_4f0c9a2b7d1e8c3a5b6f7e21",
  "event": "message.received",
  "created_at": 1759160000,
  "data": {
    "message_id": "wamid.HBgMNTU0MTk5...",
    "channel": "whatsapp_oficial",
    "phone": "5541999998888",
    "name": "Ana Souza",
    "text": "Quero saber mais sobre o plano",
    "type": "text",
    "lead_id": 8812,
    "received_at": 1759160000
  }
}

Verificar a assinatura

X-SalesBoost-Signature traz t (unix) e v1 = HMAC-SHA256, em hex, de "{t}.{corpo cru}" com o secret do webhook. Compare em tempo constante e recuse t com mais de 5 minutos, para ninguém reaproveitar uma requisição antiga.

// PHP
$corpo = file_get_contents('php://input');          // o corpo CRU, antes de json_decode
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_SALESBOOST_SIGNATURE'] ?? ''), $s);
$esperada = hash_hmac('sha256', $s['t'] . '.' . $corpo, getenv('SB_WEBHOOK_SECRET'));
if (!hash_equals($esperada, $s['v1'] ?? '') || abs(time() - (int) $s['t']) > 300) {
    http_response_code(401); exit;
}
http_response_code(200);                            // responda rápido; processe depois
// Node (Express) — use express.raw para ter o corpo cru
const crypto = require('crypto');
app.post('/salesboost/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = Object.fromEntries(req.get('X-SalesBoost-Signature').split(',').map(p => p.split('=')));
  const esperada = crypto.createHmac('sha256', process.env.SB_WEBHOOK_SECRET)
    .update(sig.t + '.' + req.body).digest('hex');
  const ok = esperada.length === (sig.v1 || '').length
    && crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(sig.v1))
    && Math.abs(Date.now() / 1000 - Number(sig.t)) <= 300;
  if (!ok) return res.sendStatus(401);
  res.sendStatus(200);
  const evento = JSON.parse(req.body);
  // ...
});

GEThttps://app.salesboost.com.br/api/v1/webhook-events

A lista de eventos com a descrição — a mesma da tabela acima.

GEThttps://app.salesboost.com.br/api/v1/webhooks

Os webhooks da conta. O secret vem mascarado.

POSThttps://app.salesboost.com.br/api/v1/webhooks

curl -s -X POST https://app.salesboost.com.br/api/v1/webhooks \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://seu-sistema.com/salesboost/webhook",
       "events":["message.received","message.read","lead.unsubscribed","conversation.closed"],
       "description":"Meu sistema — produção"}'
{
  "data": {
    "id": 3, "url": "https://seu-sistema.com/salesboost/webhook",
    "events": ["message.received", "message.read", "lead.unsubscribed", "conversation.closed"],
    "description": "Meu sistema — produção", "active": true,
    "secret": "whsec_9c1f0e7a2b...",
    "consecutive_fails": 0, "last_status": null, "last_sent_at": null, "created_at": 1759160000
  }
}

⚠ Guarde o secret agora

Ele só aparece inteiro nesta resposta (e ao girar). Depois, só o começo.

"events": ["*"] assina todos, inclusive os que forem criados no futuro.

PATCHhttps://app.salesboost.com.br/api/v1/webhooks/{id}

Altera url, events, description ou active (false pausa; reativar zera o contador de falhas). "rotate_secret": true gera um segredo novo e o devolve inteiro — o antigo para de valer na hora.

DELETEhttps://app.salesboost.com.br/api/v1/webhooks/{id}

Remove o webhook. O que estava na fila para ele é cancelado.

POSThttps://app.salesboost.com.br/api/v1/webhooks/{id}/test

Manda um evento ping agora e devolve o que o seu servidor respondeu — o jeito de validar URL e assinatura antes de esperar um evento de verdade.

{ "delivered": true, "http_status": 200, "response_body": "ok" }

GEThttps://app.salesboost.com.br/api/v1/webhooks/{id}/deliveries

As 50 entregas mais recentes: event, status (pending, delivered, failed, cancelled), attempts, next_retry_at, http_status e o começo da resposta do seu servidor. Guardamos 30 dias.

Exemplo: tag → automação → IA → webhooks

  1. No SalesBoost, crie uma automação com o gatilho Ganhou uma tag, nome da tag analise_visualizada, e um passo Enviar mensagem pelo WhatsApp. Na API Oficial, a primeira mensagem para quem não falou com você nas últimas 24 h precisa ser um template aprovado.
  2. Seu sistema aplica a tag: POST /leads/{id}/tags (ou POST /leads com "tags", se ainda não tem o id). A automação começa e envia a mensagem.
  3. Quando o contato responde, o agente de IA do número assume a conversa — desde que o número esteja com o chatbot ligado e haja um agente ativo. Se um atendente humano assumir o chat, a IA para de responder até ele encerrar ou ficar inativo pelo tempo configurado.
  4. Você recebe message.received a cada resposta, message.read quando ele lê, lead.unsubscribed se pedir para sair e conversation.closed quando o atendimento for encerrado.

Para testar com um contato só: aplique a tag apenas nele e acompanhe em GET /webhooks/{id}/deliveries.

Endpoints legados

A primeira geração da API — /Api/message_send_text e /Api/instances_list, usada pelo Personalize e por integrações antigas — continua no ar e não muda. O que mudou é que ela agora aceita também as chaves sb_live_…, então o lojista pode ter uma credencial só.

EndpointEscopo exigido (chave v1)
POST /Api/message_send_textwhatsapp:send
POST /Api/instances_listwhatsapp:read
curl -s -X POST https://app.salesboost.com.br/Api/message_send_text \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"instance_id":"r5d9...","number":"5541996770938","text":"Olá!"}'

⚠ Duas diferenças que valem lembrar

O token antigo podia ir no corpo do JSON ({"token": "..."}); a chave sb_live_ vai só no cabeçalho — aceitar nos dois lugares faria a regra virar recomendação. E a chave v1 carrega a empresa, não um usuário: do outro lado ela é resolvida no dono da conta.

Migrar é só trocar o valor do cabeçalho Authorization por uma chave com os escopos de WhatsApp. O token antigo continua válido enquanto você quiser — revogar é decisão do lojista, não uma data que nós impomos.

Enviar uma mensagem

O instance_id identifica qual número de WhatsApp envia. O lojista encontra o dele no painel, em API & Tokens ▸ Instâncias WhatsApp.

POST https://app.salesboost.com.br/Api/message_send_text
Content-Type: application/json
Authorization: Bearer SUA_CHAVE

{
  "instance_id": "INSTANCE_ID_AQUI",
  "number":      "5541999999999",
  "text":        "Olá! Mensagem teste."
}

Com o token antigo, token também pode ir dentro do JSON — é o formato que as integrações existentes usam e ele continua aceito. Com uma chave sb_live_, só o cabeçalho.

Listar as instâncias

curl -s -X POST https://app.salesboost.com.br/Api/instances_list \
  -H "Authorization: Bearer $SB_TOKEN"

Conectar o Personalize

📌 Passo a passo

  1. No SalesBoost, em API & Tokens, copie a credencial e o Instance ID da instância desejada.
  2. No Personalize, acesse /adm/config (Painel Admin ▸ Configurações).
  3. Cole a credencial no campo Access Token e o id no campo Instance ID.
  4. Clique em Salvar configuração e depois em Enviar teste, usando seu próprio número para validar.
  5. Quando o teste chegar no WhatsApp, ative Disparar mensagens automaticamente.

Serve tanto o token antigo quanto uma chave sb_live_ com o escopo whatsapp:send — e a chave é preferível, porque dá para revogar só ela se o Personalize precisar sair do ar.

Segurança

O que esperamos de quem integra

O que fazemos do nosso lado

SalesBoost · API v1 · Emissão de chaves no painel do lojista, em API & Tokens.