API para Desenvolvedores

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
Abrir a referência da API →
API REST de WhatsApp do StartMessaging

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
Leia o guia de webhooks →
Eventos de webhook do WhatsApp assinados
Feita para Produçã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.

Endpoints

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 de Taxa

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
Planos Developer

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
Início Rápido

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'];
Webhooks

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');
}
Conversões

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')
Perguntas Frequentes

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.

WhatsApp API

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.