Para desenvolvedores
Integre em cinco chamadas
API HTTP com JSON, no mesmo contrato da uazapi. Base URL https://api.pontzap.com.br (somente HTTPS).
Todos os exemplos têm versão em cURL, JavaScript e Python — a linguagem que você escolher vale para a página inteira.
Início rápido
-
Crie uma instância
Use o
admintokenda sua conta. A resposta traz otokenda instância: guarde-o, ele autoriza todas as outras chamadas desse número.curl -X POST 'https://api.pontzap.com.br/instance/create' \ -H 'admintoken: SEU_ADMINTOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "loja-centro", "systemName": "meu-erp" }'const resposta = await fetch('https://api.pontzap.com.br/instance/create', { method: 'POST', headers: { admintoken: 'SEU_ADMINTOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "name": "loja-centro", "systemName": "meu-erp" }), }); const dados = await resposta.json(); console.log(resposta.status, dados);import requests resposta = requests.post( "https://api.pontzap.com.br/instance/create", headers={"admintoken": "SEU_ADMINTOKEN", "Content-Type": "application/json"}, json={ "name": "loja-centro", "systemName": "meu-erp", }, ) print(resposta.status_code, resposta.json())Ver resposta
200 OK{ "response": "Instance created successfully", "name": "loja-centro", "token": "7f3c2a1e-…-token-da-instancia", "instance": { "id": "r395f3fbc2622e8", "status": "disconnected", "…": "…" } } -
Gere o QR code
Sem corpo, devolve o QR code em
instance.qrcode(data URL PNG, pronto para<img src>). Com{"phone": "55…"}, devolve um código de pareamento eminstance.paircode.curl -X POST 'https://api.pontzap.com.br/instance/connect' \ -H 'token: SEU_TOKEN_DA_INSTANCIA' \ -H 'Content-Type: application/json' \ -d '{}'const resposta = await fetch('https://api.pontzap.com.br/instance/connect', { method: 'POST', headers: { token: 'SEU_TOKEN_DA_INSTANCIA', 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); const dados = await resposta.json(); console.log(resposta.status, dados);import requests resposta = requests.post( "https://api.pontzap.com.br/instance/connect", headers={"token": "SEU_TOKEN_DA_INSTANCIA", "Content-Type": "application/json"}, json={}, ) print(resposta.status_code, resposta.json())Ver resposta
200 OK{ "response": "Connecting", "loggedIn": false, "instance": { "status": "connecting", "qrcode": "data:image/png;base64,iVBORw0KGgo…", "…": "…" } } -
Espere o celular confirmar
O QR code é renovado a cada poucos segundos. Consulte o estado a cada 2–3 s, mostre o
qrcodemais recente e pare quandostatus.loggedInfortrue(chega também o webhookconnection).curl -X GET 'https://api.pontzap.com.br/instance/status' \ -H 'token: SEU_TOKEN_DA_INSTANCIA'const resposta = await fetch('https://api.pontzap.com.br/instance/status', { method: 'GET', headers: { token: 'SEU_TOKEN_DA_INSTANCIA', }, }); const dados = await resposta.json(); console.log(resposta.status, dados);import requests resposta = requests.get( "https://api.pontzap.com.br/instance/status", headers={"token": "SEU_TOKEN_DA_INSTANCIA"}, ) print(resposta.status_code, resposta.json())Ver resposta
200 OK{ "instance": { "status": "connected", "…": "…" }, "status": { "connected": true, "loggedIn": true, "jid": { "user": "5592988887777", "agent": 0, "device": 12, "server": "s.whatsapp.net" } } }Exemplo completo de espera pelo QR code:
// Mostra o QR code e espera o celular confirmar. const headers = { token: 'SEU_TOKEN_DA_INSTANCIA' }; const img = document.querySelector('#qr'); async function aguardarConexao() { for (;;) { const r = await fetch('https://api.pontzap.com.br/instance/status', { headers }); const { instance, status } = await r.json(); if (status.loggedIn) return instance; // conectado if (instance.qrcode) img.src = instance.qrcode; // QR mais recente (data URL PNG) await new Promise((ok) => setTimeout(ok, 3000)); } }import time, requests headers = {"token": "SEU_TOKEN_DA_INSTANCIA"} while True: dados = requests.get("https://api.pontzap.com.br/instance/status", headers=headers).json() if dados["status"]["loggedIn"]: break # conectado qr = dados["instance"]["qrcode"] # data URL PNG: mostre numa <img src=...> time.sleep(3) -
Envie uma mensagem
numbercom DDI e DDD. Para não perder envio se o número cair, use"async": true(veja Fila assíncrona).curl -X POST 'https://api.pontzap.com.br/send/text' \ -H 'token: SEU_TOKEN_DA_INSTANCIA' \ -H 'Content-Type: application/json' \ -d '{ "number": "5592999999999", "text": "Olá! Seu pedido #4821 saiu para entrega." }'const resposta = await fetch('https://api.pontzap.com.br/send/text', { method: 'POST', headers: { token: 'SEU_TOKEN_DA_INSTANCIA', 'Content-Type': 'application/json', }, body: JSON.stringify({ "number": "5592999999999", "text": "Olá! Seu pedido #4821 saiu para entrega." }), }); const dados = await resposta.json(); console.log(resposta.status, dados);import requests resposta = requests.post( "https://api.pontzap.com.br/send/text", headers={"token": "SEU_TOKEN_DA_INSTANCIA", "Content-Type": "application/json"}, json={ "number": "5592999999999", "text": "Olá! Seu pedido #4821 saiu para entrega.", }, ) print(resposta.status_code, resposta.json())Ver resposta
200 OK{ "id": "5592988887777:3EB0D8448DC0B9FE593A2C", "messageid": "3EB0D8448DC0B9FE593A2C", "status": "Sent", "…": "…", "response": { "status": "success", "message": "Message sent successfully" } } -
Receba as respostas
Informe a URL pública do seu sistema e os eventos. A partir daí cada mensagem recebida chega lá como POST JSON.
curl -X POST 'https://api.pontzap.com.br/webhook' \ -H 'token: SEU_TOKEN_DA_INSTANCIA' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://meusistema.com.br/webhooks/pontzap", "events": [ "messages", "messages_update", "connection" ], "excludeMessages": [ "wasSentByApi" ] }'const resposta = await fetch('https://api.pontzap.com.br/webhook', { method: 'POST', headers: { token: 'SEU_TOKEN_DA_INSTANCIA', 'Content-Type': 'application/json', }, body: JSON.stringify({ "url": "https://meusistema.com.br/webhooks/pontzap", "events": [ "messages", "messages_update", "connection" ], "excludeMessages": [ "wasSentByApi" ] }), }); const dados = await resposta.json(); console.log(resposta.status, dados);import requests resposta = requests.post( "https://api.pontzap.com.br/webhook", headers={"token": "SEU_TOKEN_DA_INSTANCIA", "Content-Type": "application/json"}, json={ "url": "https://meusistema.com.br/webhooks/pontzap", "events": ["messages", "messages_update", "connection"], "excludeMessages": ["wasSentByApi"], }, ) print(resposta.status_code, resposta.json())Ver resposta
200 OK[ { "id": "wh_db2aff2c34", "enabled": true, "url": "https://meusistema.com.br/webhooks/pontzap", "events": [ "messages", "messages_update", "connection" ], "excludeMessages": [ "wasSentByApi" ], "…": "…" } ]
Autenticação
Toda chamada leva um header com a chave certa. Não há login nem sessão.
| Header | O que é | Serve para |
|---|---|---|
admintoken | Chave da sua conta, entregue pela PontZap. | Criar e listar instâncias, campos administrativos, trocar token, webhook global. |
token | Chave de uma instância (um número), devolvida por /instance/create. | Todo o resto: conectar, enviar, buscar, webhooks, grupos. |
| nenhum | — | GET /status, /health, /health/ready. |
- Chave ausente ou errada responde 401 com
{"code":401,"message":"Invalid token.","data":{}}. - Guarde as chaves no servidor (variável de ambiente, cofre). Nunca no front-end, no app ou no repositório.
- Vazou um token? Troque com
POST /instance/token/rotate— o antigo para de valer na hora.
Respostas e erros
Respostas são JSON. Erros vêm como {"error": "mensagem"} (exceto o 401, acima) — use o status HTTP para decidir e a mensagem para registrar.
| Status | Quando |
|---|---|
| 200 | Deu certo. O corpo é JSON. |
| 400 | Pedido inválido (campo faltando, número sem WhatsApp) ou WhatsApp instance is not connected. |
| 401 | Token ou admintoken ausente ou errado. Formato próprio: {"code":401,"message":"Invalid token.","data":{}}. |
| 403 | Grupos/comunidades: o número não tem permissão (não é admin). |
| 404 | Instância, webhook global ou grupo não encontrado. Em algumas rotas de grupo, também sessão desconectada ou JID inválido. |
| 408 | /group/list: o WhatsApp não respondeu a tempo. |
| 409 | /instance/token/rotate sem confirm_impacts: true. |
| 413 | Corpo ou arquivo grande demais (File too large); descrição de grupo acima de 2048 caracteres. |
| 415 | /group/updateImage: imagem que não dá para decodificar. |
| 500 | Erro interno, ou No session em rotas que precisam da instância conectada (privacidade, agenda, bloqueios). |
| 501 | Rota do contrato uazapi que a PontZap ainda não implementou: {"error":"… (previsto para a Onda N).","code":"not_implemented"}. |
{
"error": "WhatsApp instance is not connected"
} {
"error": "Endpoint ainda não disponível na PontZap (previsto para a Onda 3).",
"code": "not_implemented"
} Fila assíncrona
Acrescente "async": true a qualquer /send/*. A API responde na hora com status: "Queued" e o
messageid definitivo; o envio sai pela fila, que fica gravada no banco.
curl -X POST 'https://api.pontzap.com.br/send/text' \
-H 'token: SEU_TOKEN_DA_INSTANCIA' \
-H 'Content-Type: application/json' \
-d '{
"number": "5592999999999",
"text": "Seu pedido #4821 saiu para entrega.",
"async": true,
"track_source": "erp",
"track_id": "pedido-4821"
}' const resposta = await fetch('https://api.pontzap.com.br/send/text', {
method: 'POST',
headers: {
token: 'SEU_TOKEN_DA_INSTANCIA',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"number": "5592999999999",
"text": "Seu pedido #4821 saiu para entrega.",
"async": true,
"track_source": "erp",
"track_id": "pedido-4821"
}),
});
const dados = await resposta.json();
console.log(resposta.status, dados); import requests
resposta = requests.post(
"https://api.pontzap.com.br/send/text",
headers={"token": "SEU_TOKEN_DA_INSTANCIA", "Content-Type": "application/json"},
json={
"number": "5592999999999",
"text": "Seu pedido #4821 saiu para entrega.",
"async": True,
"track_source": "erp",
"track_id": "pedido-4821",
},
)
print(resposta.status_code, resposta.json()) Ver resposta
{
"id": "3EB0D8448DC0B9FE593A2C",
"messageid": "3EB0D8448DC0B9FE593A2C",
"chatid": "5592999999999@s.whatsapp.net",
"fromMe": true,
"isGroup": false,
"messageType": "Conversation",
"source": "api",
"messageTimestamp": 1791933175740,
"status": "Queued",
"text": "Seu pedido #4821 saiu para entrega.",
"wasSentByApi": true,
"sendFunction": "/send/text",
"sendPayload": {
"number": "5592999999999",
"text": "Seu pedido #4821 saiu para entrega."
},
"track_source": "erp",
"track_id": "pedido-4821",
"response": {
"status": "success",
"message": "Message queued"
}
} - Ordem garantida: um trabalhador por instância, na ordem de chegada.
- Número desconectado: a fila espera reconectar, sem gastar tentativas (
queue.status: "waiting_connection"). - Erro do WhatsApp: 3 tentativas (de novo após 5 s e 30 s). Se não der, a mensagem fica
Failede chega o webhookmessagescomstatus: "Failed"eerror. - Reinício do servidor: a fila continua de onde parou.
- Ritmo:
/instance/updateDelaySettingsdefine um intervalo aleatório entre envios;delay(“digitando…”) roda na fila, sem prender a requisição. - Acompanhar e cancelar:
GET /message/asynceDELETE /message/async(as canceladas ficamCanceled). - Rastrear: mande
track_source/track_ide encontre a mensagem com/message/find.
Eventos de webhook
A PontZap faz POST com JSON na URL configurada em /webhook
(ou no webhook global, que recebe de todas as instâncias).
Eventos
| Evento | Quando chega |
|---|---|
connection | O número conectou, desconectou ou caiu. Traz instance.status e, ao desconectar, o motivo. |
messages | Mensagem nova, recebida ou enviada (inclusive pela API, com wasSentByApi: true). Traz message e chat. Envio da fila que falhou chega aqui com status: "Failed". |
messages_update | Mudança de estado: entregue, lida, tocada, apagada (Deleted). |
history | Histórico que o celular manda ao conectar, em lotes de chats, messages e status. |
chats | Conversa criada ou alterada (arquivada, fixada, silenciada, lida). |
groups | Mudanças em grupos avisadas pelo WhatsApp (o evento original vai em event). |
newsletter_messages | Posts de canais. Só chega se for assinado pelo nome. |
status_posts | Status (stories) dos contatos. Só chega se for assinado pelo nome. |
Formato
Todo evento traz o envelope EventType, owner (número da instância), token, BaseUrl e instanceName, mais os campos do evento na raiz.
{
"EventType": "messages",
"owner": "5592988887777",
"token": "7f3c2a1e-…-token-da-instancia",
"BaseUrl": "https://api.pontzap.com.br",
"instanceName": "loja-centro",
"message": {
"id": "5592988887777:3EB0C1A2B3C4D5E6F7",
"messageid": "3EB0C1A2B3C4D5E6F7",
"chatid": "5592999999999@s.whatsapp.net",
"fromMe": false,
"isGroup": false,
"messageType": "Conversation",
"source": "",
"messageTimestamp": 1791933175740,
"status": "",
"text": "Pode entregar depois das 14h?",
"wasSentByApi": false,
"sendFunction": "",
"track_source": "",
"track_id": "",
"content": "Pode entregar depois das 14h?",
"sender": "5592999999999@s.whatsapp.net",
"senderName": "Maria",
"owner": "5592988887777"
},
"chat": {
"wa_chatid": "5592999999999@s.whatsapp.net",
"wa_name": "Maria",
"wa_unreadCount": 1,
"…": "…"
},
"chatSource": "updated"
} {
"EventType": "messages_update",
"owner": "5592988887777",
"token": "7f3c2a1e-…-token-da-instancia",
"BaseUrl": "https://api.pontzap.com.br",
"instanceName": "loja-centro",
"type": "ReadReceipt",
"state": "Read",
"event": {
"Chat": "5592999999999@s.whatsapp.net",
"chatid": "5592999999999@s.whatsapp.net",
"Sender": "5592999999999@s.whatsapp.net",
"MessageIDs": [
"3EB0D8448DC0B9FE593A2C"
],
"Timestamp": 1791933240,
"Type": "Read",
"IsFromMe": false,
"IsGroup": false
}
} {
"EventType": "connection",
"owner": "5592988887777",
"token": "7f3c2a1e-…-token-da-instancia",
"BaseUrl": "https://api.pontzap.com.br",
"instanceName": "loja-centro",
"event_id": "b8f0…",
"type": "Disconnected",
"instance": {
"name": "loja-centro",
"status": "disconnected",
"lastDisconnectReason": "disconnected by request",
"lastDisconnect": "2026-10-13 23:13:01.585Z"
}
} Atenção: o envelope inclui o token da instância. Use HTTPS na sua URL e não grave o corpo bruto em logs abertos.
Headers da entrega
Content-Type: application/jsoneUser-Agent: PontZap-Webhook/1.0.X-PontZap-Delivery: id da entrega, igual em todas as tentativas. Use para não processar o mesmo evento duas vezes.
Novas tentativas
Sucesso é qualquer resposta 2xx em até 15 segundos. Erro de rede, timeout, 408, 429 e 5xx são tentados de novo — até 6 tentativas no total, com esperas de:
- 1ªna hora
- 2ª+5 s
- 3ª+30 s
- 4ª+2 min
- 5ª+10 min
- 6ª+30 min
- Outros 4xx (400, 401, 404…) falham na primeira tentativa: corrija o seu endpoint.
- O que falhar de vez aparece em
GET /webhook/errors(últimas 20, com o payload). - A fila de entregas fica no banco: um reinício do servidor não perde eventos. Entregas concluídas são apagadas após 24 h; falhas, após 7 dias.
- A URL precisa ser pública.
localhoste IPs de rede interna são recusados — para testar na sua máquina, use um túnel (ngrok, Cloudflare Tunnel).
Receptor mínimo
// Node 18+, sem dependências.
import { createServer } from 'node:http';
const vistos = new Set(); // troque por banco/Redis em produção
createServer(async (req, res) => {
if (req.method !== 'POST' || req.url !== '/webhooks/pontzap') return res.writeHead(404).end();
let corpo = '';
for await (const parte of req) corpo += parte;
const entrega = req.headers['x-pontzap-delivery']; // igual em todas as tentativas
res.writeHead(200).end(); // responda rápido (até 15 s)
if (vistos.has(entrega)) return; // já processado
vistos.add(entrega);
const evento = JSON.parse(corpo);
if (evento.EventType === 'messages' && !evento.message.fromMe) {
console.log(evento.message.chatid, evento.message.text);
}
}).listen(3000); # pip install flask
from flask import Flask, request
app = Flask(__name__)
vistos = set() # troque por banco/Redis em produção
@app.post("/webhooks/pontzap")
def pontzap():
entrega = request.headers.get("X-PontZap-Delivery") # igual em todas as tentativas
if entrega in vistos:
return "", 200
vistos.add(entrega)
evento = request.get_json()
if evento["EventType"] == "messages" and not evento["message"]["fromMe"]:
print(evento["message"]["chatid"], evento["message"]["text"])
return "", 200 # responda 2xx em até 15 s Compatibilidade com a uazapi
Mesmos caminhos, mesmos headers (token, admintoken), mesmos corpos e eventos de webhook.
Para migrar uma integração que já funciona:
- Troque a base URL para
https://api.pontzap.com.br. - Crie as instâncias na PontZap e troque os tokens no seu sistema (eles são diferentes dos da uazapi).
- Escaneie o QR code de cada número de novo.
- Configure os webhooks (mesmo formato) e confira os primeiros eventos.
Confira se as rotas que você usa estão na referência. As que ainda não chegaram respondem 501 com a onda prevista — veja a lista em Em breve.
Diferenças conhecidas
linkPreview*,readchat,readmessagese placeholders como{{name}}são aceitos e ignorados.replyidcita a mensagem só pelo id (sem copiar o conteúdo original).pttexige o arquivo já em OGG/Opus;/message/downloadnão converte para MP3 (generate_mp3) nem transcreve (transcribe)./message/statusresponde com os recibos gravados no banco (source: "memory"); não consulta o celular. Vantagem: a uazapi guarda só 24 h em memória.POST /instance/updateInstanceNamecom nome vazio responde 400.- A instância sai com
current_presence(schema) ecurrentPresence(exemplos da documentação deles). message.idsai comoowner:messageid, como nos exemplos de webhook da uazapi./chat/deletecomclearChatWhatsAppdevolve o erro emerrors(limpar sem apagar ainda não é suportado)./chat/totalcount:label_countseattendant_countssaem vazios (etiquetas e CRM chegam nas próximas ondas)./contact/adde/contact/removerespondem 501 por enquanto.- Grupos: sessão desconectada, JID inválido e timeout respondem 404 em rotas cujo contrato não prevê 400/500;
/group/listcom timeout → 408. - Grupos:
image_url/picture_*não são preenchidos eparticipant_profilessai vazio. - Webhook
groups: o evento do WhatsApp vai emevent(sem amostra real da uazapi para comparar). - Vantagem: os webhooks fazem novas tentativas automáticas; a uazapi documenta que não repete entregas que falharam.