API REST e Webhooks do WhatsApp Feitos para Integrações
Envie templates e mensagens, sincronize contatos, tags e conversas e receba um webhook assinado a cada resposta e confirmação de entrega. REST simples em /api/v1 com chaves de API com escopos, na plataforma oficial WhatsApp Business. Nenhum SDK para instalar. A API está em todos os planos, a partir do Developer Free com 100 mensagens de API por mês.
✦ Teste grátis de 14 dias · Seu próprio número do WhatsApp Business · A Meta cobra as mensagens diretamente
Uma API REST para Todo o Seu CRM de WhatsApp
Autentique-se com uma chave de API do workspace (Authorization: Bearer sm_live_…) e chame /api/v1. Envie templates, texto, mídia e mensagens interativas; crie, atualize e marque contatos com tags; leia tags, etapas do funil, templates e campanhas; liste, atribua e resolva conversas; gerencie endpoints de webhook; e informe vendas como conversões. Toda resposta de sucesso usa o mesmo envelope {data, meta}, e todo erro traz um código estável para você tratar.
- Chaves criadas em Desenvolvedores → Chaves de API e exibidas uma única vez
- 10 escopos, com os modelos Acesso total, Somente envio, Somente leitura e Personalizado
- As cobranças de mensagens são feitas pela Meta na sua própria conta do WhatsApp Business; a API nunca cobra por mensagem
Webhooks Assinados em que Você Pode Confiar
Inscreva um endpoint HTTPS em qualquer um dos 12 eventos: mensagens recebidas, enviadas, entregues, lidas e com falha; contatos criados, descadastrados e recadastrados; conversas atribuídas e resolvidas; mudanças de status de templates; e campanhas concluídas. Cada entrega é assinada com HMAC-SHA256 no cabeçalho X-SM-Signature, reenviada após 1m, 5m, 30m, 2h, 6h e 12h e listada em um registro de entregas onde você pode reenviá-la.
- Entregue pelo menos uma vez: elimine duplicatas pelo id do evento
- Endpoints que falham seguidamente (50 vezes seguidas ou por 24 horas) são desativados e sinalizados
- Troque o segredo de assinatura com 24 horas de sobreposição
Os Detalhes de que as Integrações Dependem
-
Envios idempotentes
Envie um Idempotency-Key em um POST. A mesma chave e o mesmo corpo em até 24 horas retornam a resposta original com Idempotent-Replayed: true, então uma nova tentativa nunca envia duas vezes.
-
Paginação por cursor e sincronização
As listas aceitam limit (1–100) e cursor e retornam nextCursor. Em contatos e conversas, updatedSince retorna só o que mudou, do mais antigo para o mais recente, para sincronização incremental.
-
Suas próprias referências
Adicione um clientReference, como o id do seu pedido, a um envio. Ele volta na resposta, em GET /messages/{id} e em todo webhook de enviado, entregue, lido e com falha.
-
Chaves com privilégio mínimo
Dê a cada integração sua própria chave, só com os escopos de que precisa. Um webhook criado com uma chave recebe uma família de eventos apenas enquanto essa chave tiver o escopo de leitura correspondente.
-
Registros de requisições e entregas
Veja no painel as últimas 500 requisições à API dos últimos 7 dias, filtradas por chave ou só erros, e reenvie qualquer webhook pelo registro de entregas do endpoint.
-
Referência OpenAPI
Um documento OpenAPI JSON em /api/v1/openapi.json e o Swagger UI em /api/v1/docs, com o escopo de cada operação listado como x-scope. A mesma referência está no painel, em Desenvolvedores.
O que a API Cobre
Todos os caminhos ficam em /api/v1. Criar templates e campanhas, por enquanto, é feito só pelo painel: a API os lê e envia templates aprovados uma mensagem por vez.
| Recurso | Endpoints | Escopo | O que faz |
|---|---|---|---|
| Conta | GET /me, GET /phone-numbers | Qualquer chave válida | Workspace, plano, escopos da chave, limites e uso em tempo real; seus números com classificação de qualidade e limite de mensagens |
| Mensagens | POST /messages, GET /messages/{id} | messages:send, messages:read | Mensagens de template (incluindo carrosséis e ofertas por tempo limitado), texto, imagem, vídeo, áudio, documento e interativas (interativas exigem Team ou Business); status, motivo de falha e categoria de preço da Meta |
| Contatos | GET/POST /contacts, GET/PATCH/DELETE /contacts/{idOrPhone} | contacts:read, contacts:write | Criar ou atualizar pelo número de telefone, tags, atributos personalizados, opt-in e opt-out; colocar um contato em uma etapa do funil exige Business |
| Tags e funil | GET /tags, GET /pipeline-stages | contacts:read | Tags com contagem de contatos; as etapas do seu funil de leads |
| Templates | GET /templates, GET /templates/{id} | templates:read | Somente leitura: templates são criados no painel e revisados pela Meta |
| Conversas | GET /conversations, GET /conversations/{id}, GET /conversations/{id}/messages, PATCH /conversations/{id} | conversations:read, conversations:write | Histórico de mensagens e windowOpen; atribuir, remover atribuição, resolver e reabrir (a leitura funciona em todos os planos; alterações exigem Team ou Business) |
| Campanhas | GET /campaigns, GET /campaigns/{id} | campaigns:read | Estatísticas somente leitura; campanhas são criadas e lançadas no painel |
| Conversões | POST /conversions | conversions:write | Informe uma venda para que a campanha que trouxe o cliente receba o crédito (Business) |
| Webhooks | GET/POST /webhooks, DELETE /webhooks/{id} | webhooks:manage | Inscrever e cancelar endpoints (REST hooks) |
Limites da API por Plano
Contados por workspace, não por chave. As respostas contadas trazem os cabeçalhos RateLimit-* e todo 429 traz Retry-After. As mensagens de API por mês contam só o que é enviado pela API; passada a franquia, um envio recebe 402 PLAN_MESSAGE_LIMIT_REACHED. O limite de mensagens da própria Meta para o seu número continua valendo.
| Plano | Requisições / minuto | Envios pela API / hora | Mensagens de API / mês | Endpoints de webhook |
|---|---|---|---|---|
| Teste grátis (14 dias) | 300 | 100 | 1.000 | 5 |
| Developer Free | 60 | 2.000 | 100 | 2 |
| Developer 1K | 120 | 10.000 | 1.000 | 2 |
| Developer 10K | 300 | 10.000 | 10.000 | 2 |
| Developer 100K | 1.200 | 10.000 | 100.000 | 2 |
| Team | 300 | 10.000 | Ilimitado (uso justo) | 1 |
| Business | 1.200 | 10.000 | Ilimitado (uso justo) | 5 |
Só a API? Escolha um tamanho Developer
Os planos Developer são a API com um painel somente leitura: 1 assento, 2 endpoints de webhook e as mensagens de API por mês mostradas aqui. Os preços incluem GST, e um ano custa dez meses. Os tamanhos pagos nunca bloqueiam templates de autenticação.
| Plano | Preço / mês | Mensagens de API / mês | Requisições / minuto |
|---|---|---|---|
| Developer Free | ₹0 | 100 | 60 |
| Developer 1K | ₹100 | 1.000 | 120 |
| Developer 10K | ₹499 | 10.000 | 300 |
| Developer 100K | ₹999 | 100.000 | 1.200 |
Verifique sua chave e envie um template
GET /me não exige escopo. O envio exige messages:send e um template aprovado.
# 1. Check your key: workspace, plan, scopes, limits and usage
curl https://waba-api.startmessaging.com/api/v1/me \
-H "Authorization: Bearer sm_live_your_api_key"
# 2. Send an approved template, safe to retry with the same Idempotency-Key
curl -X POST https://waba-api.startmessaging.com/api/v1/messages \
-H "Authorization: Bearer sm_live_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10421-ready" \
-d '{
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
}' const API = 'https://waba-api.startmessaging.com/api/v1';
const auth = { Authorization: 'Bearer sm_live_your_api_key' };
// 1. Check your key
const me = await fetch(`${API}/me`, { headers: auth }).then((r) => r.json());
console.log(me.data.plan.code, me.data.apiKey.scopes, me.data.limits);
// 2. Send an approved template, safe to retry with the same Idempotency-Key
const res = await fetch(`${API}/messages`, {
method: 'POST',
headers: {
...auth,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-10421-ready',
},
body: JSON.stringify({
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
}),
});
const { data } = await res.json();
console.log(data.id, data.status, res.headers.get('Idempotent-Replayed')); import requests
API = 'https://waba-api.startmessaging.com/api/v1'
AUTH = {'Authorization': 'Bearer sm_live_your_api_key'}
# 1. Check your key
me = requests.get(f'{API}/me', headers=AUTH).json()['data']
print(me['plan']['code'], me['apiKey']['scopes'], me['limits'])
# 2. Send an approved template, safe to retry with the same Idempotency-Key
res = requests.post(
f'{API}/messages',
headers={**AUTH, 'Idempotency-Key': 'order-10421-ready'},
json={
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
},
)
data = res.json()['data']
print(data['id'], data['status']) <?php
$api = 'https://waba-api.startmessaging.com/api/v1';
$auth = 'Authorization: Bearer sm_live_your_api_key';
// 1. Check your key
$ch = curl_init("$api/me");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [$auth],
]);
$me = json_decode(curl_exec($ch), true)['data'];
echo $me['plan']['code'] . ' ' . implode(',', $me['apiKey']['scopes']) . "\n";
// 2. Send an approved template, safe to retry with the same Idempotency-Key
$body = <<<'JSON'
{
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
}
JSON;
$ch = curl_init("$api/messages");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
$auth,
'Content-Type: application/json',
'Idempotency-Key: order-10421-ready',
],
CURLOPT_POSTFIELDS => $body,
]);
$data = json_decode(curl_exec($ch), true)['data'];
echo $data['id'] . ' ' . $data['status']; Verifique cada webhook antes de confiar nele
X-SM-Signature é v1=HMAC-SHA256(secret, timestamp + "." + corpo bruto). Use o segredo whsec_ inteiro como chave.
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
const app = express();
const SECRET = process.env.SM_WEBHOOK_SECRET; // the whole whsec_… string
function verify(rawBody, headers) {
const ts = headers['x-sm-timestamp'];
const sigs = headers['x-sm-signature'];
if (!/^\d+$/.test(ts ?? '') || typeof sigs !== 'string') return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5-minute tolerance
const expected = createHmac('sha256', SECRET).update(`${ts}.`).update(rawBody).digest();
return sigs.split(',').some((part) => {
const m = /^v1=([0-9a-f]{64})$/.exec(part.trim());
return m !== null && timingSafeEqual(Buffer.from(m[1], 'hex'), expected);
});
}
app.post('/webhooks/startmessaging', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(req.body, req.headers)) return res.sendStatus(401);
res.sendStatus(200); // answer 2xx within 5 seconds, then do the work
const event = JSON.parse(req.body);
// Delivered at least once: skip event.id if you have already handled it.
if (event.type === 'message.delivered') {
console.log(event.data.message.clientReference, 'delivered');
}
}); import hmac, hashlib, json, os, time
from flask import Flask, request
app = Flask(__name__)
SECRET = os.environ['SM_WEBHOOK_SECRET'].encode() # the whole whsec_… string
def verify(raw_body, ts, sigs):
if not ts or not ts.isdigit() or not sigs:
return False
if abs(time.time() - int(ts)) > 300: # 5-minute tolerance
return False
expected = hmac.new(SECRET, f'{ts}.'.encode() + raw_body, hashlib.sha256).hexdigest()
return any(
hmac.compare_digest(part.strip()[3:], expected)
for part in sigs.split(',') if part.strip().startswith('v1=')
)
@app.post('/webhooks/startmessaging')
def startmessaging_webhook():
raw = request.get_data()
if not verify(raw, request.headers.get('X-SM-Timestamp'), request.headers.get('X-SM-Signature')):
return '', 401
event = json.loads(raw)
# Delivered at least once: skip event['id'] if you have already handled it.
if event['type'] == 'message.delivered':
print(event['data']['message']['clientReference'], 'delivered')
return '', 200 <?php
$secret = getenv('SM_WEBHOOK_SECRET'); // the whole whsec_… string
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_SM_TIMESTAMP'] ?? '';
$sigs = $_SERVER['HTTP_X_SM_SIGNATURE'] ?? '';
function sm_verify(string $raw, string $ts, string $sigs, string $secret): bool
{
if (!ctype_digit($ts) || $sigs === '') return false;
if (abs(time() - (int) $ts) > 300) return false; // 5-minute tolerance
$expected = hash_hmac('sha256', $ts . '.' . $raw, $secret);
foreach (explode(',', $sigs) as $part) {
$part = trim($part);
if (strncmp($part, 'v1=', 3) === 0 && hash_equals($expected, substr($part, 3))) {
return true;
}
}
return false;
}
if (!sm_verify($raw, $ts, $sigs, $secret)) {
http_response_code(401);
exit;
}
http_response_code(200); // answer 2xx within 5 seconds
$event = json_decode($raw, true);
// Delivered at least once: skip $event['id'] if you have already handled it.
if ($event['type'] === 'message.delivered') {
error_log($event['data']['message']['clientReference'] . ' delivered');
} Informe uma venda e credite a campanha
No plano Business, POST /api/v1/conversions credita a venda à última campanha entregue a esse número dentro da sua janela de conversão (7 dias se você não alterar), e o relatório da campanha soma o valor.
# Business plan, scope conversions:write
curl -X POST https://waba-api.startmessaging.com/api/v1/conversions \
-H "Authorization: Bearer sm_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"phone": "+919812345678",
"orderId": "ORD-10442",
"value": 2499.5,
"currency": "INR",
"occurredAt": "2026-10-12T10:30:00+05:30"
}'
# 201 = recorded now, 200 = this orderId was reported already const res = await fetch('https://waba-api.startmessaging.com/api/v1/conversions', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.SM_API_KEY, // sm_live_your_api_key
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone: '+919812345678',
orderId: 'ORD-10442', // the same orderId again never counts the sale twice
value: 2499.5,
currency: 'INR',
}),
});
const { data } = await res.json();
console.log(data.created, data.conversion.campaign?.name ?? 'no campaign credited'); import os, requests
res = requests.post(
'https://waba-api.startmessaging.com/api/v1/conversions',
headers={'Authorization': f"Bearer {os.environ['SM_API_KEY']}"}, # sm_live_your_api_key
json={
'phone': '+919812345678',
'orderId': 'ORD-10442',
'value': 2499.5,
'currency': 'INR',
},
)
data = res.json()['data']
campaign = data['conversion']['campaign']
print(data['created'], campaign['name'] if campaign else 'no campaign credited') API do WhatsApp — Perguntas Frequentes
Existe um SDK?
Não. A API é REST simples sobre HTTPS com JSON, então qualquer linguagem ou ferramenta capaz de fazer uma requisição HTTPS funciona. A documentação mostra cURL, Node.js, Python e PHP, e o documento OpenAPI descreve todos os campos de requisição e resposta.
Quais planos incluem a API?
Todos os planos, inclusive o Developer Free. Os planos Developer são a API com um painel somente leitura, dimensionados por mensagens de API por mês: Developer Free com 100, depois 1K, 10K e 100K. Team e Business acrescentam a caixa de entrada compartilhada, os contatos e as campanhas, sem limite mensal de mensagens de API dentro do uso justo. O teste grátis de 14 dias inclui a API com 1.000 mensagens de API. As requisições por minuto e os endpoints de webhook dependem do plano, e GET /me mostra seu plano, os limites e a contagem do mês.
Posso criar templates ou campanhas pela API?
Ainda não. Templates são criados no painel e revisados pela Meta; campanhas são montadas e lançadas no assistente de campanhas do painel. A API lê ambos, envia templates aprovados uma mensagem por vez com POST /messages e avisa quando uma campanha termina com o webhook campaign.completed.
A API pode enviar carrosséis e ofertas por tempo limitado?
Sim. Para um template carrossel, envie template.carouselCards com uma entrada por cartão, na ordem, cada uma com seu headerMediaUrl (https) e, opcionalmente, parameters e buttonParameters. Para uma oferta por tempo limitado, defina template.offerExpiresAt com um horário ISO 8601 futuro, e o WhatsApp mostra uma contagem regressiva ao cliente. Campanhas não enviam esses templates, então eles saem um por vez pela API ou pela caixa de entrada.
O que acontece quando a franquia mensal de mensagens de API acaba?
Os planos Developer e o teste grátis incluem um número de mensagens de API por mês, contado a partir do dia 1º; só contam as mensagens enviadas pela API. O Developer Free e o teste param na franquia. Um plano Developer pago para as demais mensagens em três vezes o seu tamanho e nunca bloqueia templates de autenticação. Um envio além do limite recebe 402 PLAN_MESSAGE_LIMIT_REACHED com sent, stopAt e resetsAt nos detalhes, e GET /me mostra a contagem antes disso. Team e Business não têm limite mensal de mensagens de API (uso justo).
Como conecto Zapier, Make ou Pabbly Connect?
Ainda não há apps oficiais do StartMessaging nesses marketplaces. Use os módulos genéricos: um gatilho de catch-hook ou webhook personalizado para receber eventos e uma ação de requisição HTTP com sua chave de API para chamar a API. Quem cria o próprio app pode se inscrever e cancelar com POST /webhooks e DELETE /webhooks/{id}.
O que acontece se eu repetir um envio?
Com o mesmo Idempotency-Key e o mesmo corpo em até 24 horas, você recebe a resposta original com Idempotent-Replayed: true, e nada é enviado duas vezes. Reutilizar uma chave com um corpo diferente é recusado com IDEMPOTENCY_KEY_REUSED. Após um 429 ou 5xx, tente de novo com a mesma chave.
Como funcionam os limites de taxa?
Os limites são por workspace, então mais chaves nunca aumentam a vazão. Requisições por minuto: 60 no Developer Free, 120 no Developer 1K, 300 no Developer 10K, no Team e no teste grátis, e 1.200 no Developer 100K e no Business. Envios pela API por hora: 10.000 nos planos pagos, 2.000 no Developer Free e 100 no teste. Um envio conta nos dois limites, e um envio recusado devolve sua cota por hora. Um 429 informa em Retry-After quanto tempo esperar. O limite de mensagens da própria Meta para o seu número vale por cima disso.
O StartMessaging cobra por mensagem enviada pela API?
Não. A StartMessaging é Meta Tech Provider: você conecta sua própria Conta do WhatsApp Business, e a Meta cobra as mensagens diretamente nela, pelas tarifas da Meta. A Meta cobra todo template entregue, inclusive os de utilidade mesmo dentro da janela de 24 horas, e as respostas de atendimento além de uma franquia mensal grátis. A StartMessaging cobra um plano fixo, sem carteira e sem acréscimo por mensagem; os planos Developer são dimensionados pela quantidade de mensagens de API por mês que incluem. O campo pricing de cada mensagem mostra como a Meta a classificou.
Crie Sua Integração com o WhatsApp
Seu primeiro workspace ganha um teste grátis de 14 dias; depois dele, o Developer Free mantém a API ativa com 100 mensagens por mês. Conecte seu próprio número do WhatsApp Business, crie uma chave e faça sua primeira chamada.