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:
| Escopo | Permite |
|---|---|
leads:read | Listar e ler leads e os estágios do funil |
leads:write | Criar, alterar e mover leads no funil |
contacts:read | Listar e ler contatos do CRM |
contacts:write | Criar e alterar contatos do CRM |
deals:read | Listar e ler oportunidades do CRM e o pipeline |
deals:write | Criar, alterar e mover oportunidades do CRM |
email:send | Enviar e-mail em nome da empresa |
sms:send | Enviar SMS em nome da empresa |
whatsapp:read | Listar instâncias, números e mensagens (templates) da API Oficial |
whatsapp:templates | Criar, editar e excluir mensagens (templates) da API Oficial |
whatsapp:send | Enviar 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"
}
}
| HTTP | code | Quando |
|---|---|---|
| 400 | insecure_transport | Chamada sem HTTPS |
| 400 | invalid_body | JSON inválido ou acima de 256 KB |
| 401 | unauthorized | Chave ausente, inválida, revogada, expirada ou fora dos IPs |
| 401 | token_in_url | Chave mandada na query string |
| 403 | insufficient_scope | A chave não tem o escopo necessário |
| 404 | not_found | Recurso inexistente neste workspace |
| 409 | idempotency_conflict | Mesma Idempotency-Key com outro corpo |
| 422 | validation_error | Campo faltando ou inválido |
| 422 | use_move_endpoint | Tentou trocar de estágio pelo PATCH |
| 429 | rate_limited | Passou do limite por minuto |
| 429 | too_many_attempts | Erros 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âmetro | Descrição |
|---|---|
q | Busca por nome ou e-mail |
email | E-mail exato |
phone | Telefone (compara pelos últimos 8 dígitos) |
stage | Chave do estágio |
updated_since | Timestamp — para sincronizar só o que mudou |
page, per_page | Paginaçã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": []
}
- Só conta como
addeda tag que o lead ainda não tinha. Reaplicar a mesma tag não inicia a automação de novo — para repetir o fluxo, remova e aplique outra vez. - Até 20 tags por chamada, 60 caracteres cada. Vírgula vira espaço (a lista é guardada separada por vírgula).
- Contato descadastrado não entra em automação, mesmo ganhando a tag.
- Não tem o id? Crie ou ache o lead com
POST /leads(por e-mail ou telefone) e já mande"tags"no mesmo corpo.
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.
- É a forma indicada para integração por WhatsApp e para Custom Actions da IA, que conhecem o telefone e não o id interno.
- O telefone casa pelos últimos 8 dígitos: DDI e nono dígito não atrapalham.
- Funciona mesmo sem lead cadastrado — a supressão fica no próprio número,
e a automação para do mesmo jeito. Nesse caso
lead_idvoltanull.
⚠ 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ódigo | HTTP | O que fazer |
|---|---|---|
whatsapp_not_configured | 409 | A conta não tem número oficial configurado, ou falta o WABA ID nas credenciais |
invalid_template | 422 | Alguma regra da Meta não fecha; a mensagem diz qual |
template_conflict | 409 | Nome repetido no idioma, ou status que não permite a operação |
meta_rejected | 422 | A Meta recusou; a mensagem dela vem junto |
meta_unavailable | 503 | Problema 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.
- Escopo da chave:
webhooks:manage(crie uma chave com ele em Configurações → API). - Só HTTPS, e nunca endereço interno (localhost, rede privada).
- Entrega em até ~1 minuto. Responda 2xx em até 10 segundos — processe depois, se precisar.
- Sem 2xx, tentamos de novo em 1 min, 5 min, 30 min, 2 h e 6 h; depois desistimos daquela entrega.
O histórico fica em
GET /webhooks/{id}/deliveries. - Uma mesma entrega pode chegar mais de uma vez (a rede falha). Use o
iddo evento (evt_…, também no cabeçalhoX-SalesBoost-Delivery) para ignorar repetidos. - Até 10 webhooks por conta.
Eventos
| Evento | Quando | data |
|---|---|---|
message.received | O contato mandou mensagem (respondeu) | message_id, channel, phone, name, text, type, lead_id, received_at |
message.delivered | Mensagem enviada chegou ao aparelho | message_id, channel, status, phone, lead_id, at |
message.read | Mensagem enviada foi lida (visualizada); áudio ouvido conta como lida | |
message.failed | Mensagem enviada falhou | |
conversation.closed | Atendimento encerrado no Chat WhatsApp | phone, name, lead_id, stage, closed_by, closed_at |
lead.unsubscribed | Contato pediu descadastro (pela API ou por "SAIR" no WhatsApp) | lead_id, phone, email, source, text, at |
lead.tag_added | Tag aplicada ao lead | lead, tags, source |
lead.tag_removed | Tag 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
- 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. - Seu sistema aplica a tag:
POST /leads/{id}/tags(ouPOST /leadscom"tags", se ainda não tem o id). A automação começa e envia a mensagem. - 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.
- Você recebe
message.receiveda cada resposta,message.readquando ele lê,lead.unsubscribedse pedir para sair econversation.closedquando 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ó.
| Endpoint | Escopo exigido (chave v1) |
|---|---|
POST /Api/message_send_text | whatsapp:send |
POST /Api/instances_list | whatsapp: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
- No SalesBoost, em API & Tokens, copie a credencial e o Instance ID da instância desejada.
- No Personalize, acesse
/adm/config(Painel Admin ▸ Configurações). - Cole a credencial no campo Access Token e o id no campo Instance ID.
- Clique em Salvar configuração e depois em Enviar teste, usando seu próprio número para validar.
- 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
- Guarde a chave fora do código. Variável de ambiente ou cofre de segredos, nunca no repositório, em arquivo de configuração versionado ou no front-end.
- Nada de navegador. A API não manda cabeçalho de CORS: é feita para servidor-a-servidor. Chamar do JavaScript do seu site exigiria expor a chave a qualquer visitante.
- Peça restrição de IP dos seus servidores de saída, e prazo de validade. Com as duas, uma chave vazada tem uso curto e de um lugar só.
- Uma chave por ambiente (produção, homologação) e por cliente. Revogar uma não pode derrubar o resto.
- Não registre a chave em log — inclusive em log de erro de biblioteca HTTP, que costuma imprimir cabeçalhos.
- Suspeitou, avise. A revogação vale na chamada seguinte.
O que fazemos do nosso lado
- O segredo não é guardado — o banco tem só um HMAC-SHA256 dele. Nem nós conseguimos mostrar a chave de novo depois de criada.
- A conferência do segredo é em tempo constante, e o identificador da chave viaja separado dele.
- Cada consulta é restrita à empresa da chave no próprio SQL, e a resposta é montada campo a campo — a linha do banco nunca vira JSON.
- Toda chamada é registrada com quem, quando, o quê e o resultado — nunca o corpo, que é dado do cliente final do lojista.
- Erros de autenticação demais de um mesmo IP bloqueiam aquele IP temporariamente.
SalesBoost · API v1 · Emissão de chaves no painel do lojista, em API & Tokens.