Webhooks do CRM
Um webhook do CRM é configurado por funil, no painel Maestron (CRM → abrir um funil → Configurar funil → aba Webhooks). Ele dispara um POST assinado pra uma URL https:// sua toda vez que um evento assinado acontece em qualquer negócio daquele funil — criado pelo app, pela API pública ou por um formulário de captação.
Se preferir puxar em vez de receber, o feed de eventos abaixo é a mesma informação, por API Key, sem precisar expor uma URL pública.
Eventos
Seção intitulada “Eventos”| Evento | Dispara quando |
|---|---|
lead.created | Um negócio é criado. |
lead.updated | Qualquer campo do negócio muda (inclusive junto com stage_changed/won/lost abaixo). |
lead.stage_changed | O negócio muda de estágio. |
lead.won | O status muda para um valor cujo tipo (kind) é ganho. |
lead.lost | O status muda para um valor cujo tipo (kind) é perdido. |
lead.deleted | O negócio é removido. |
lead.comment_added | Um comentário é adicionado ao negócio. |
lead.activity_created | Uma atividade é criada no negócio. |
lead.activity_completed | Uma atividade do negócio é marcada como concluída. |
Um negócio que muda de etapa dispara os dois lead.stage_changed e lead.updated — assine só o que precisar.
Configurar
Seção intitulada “Configurar”Na aba Webhooks, “Adicionar webhook” pede:
- Nome e URL — só
https://. - Eventos — quais da lista acima este endpoint recebe.
- Campos enviados — por padrão todos os campos do negócio; você pode restringir a uma lista (o
iddo negócio sempre vem, mesmo restringindo). - Ativo.
Ao salvar, o painel mostra o segredo (whsec_...) — usado pra verificar a assinatura. “Enviar teste” dispara um ping síncrono pro endpoint. Um endpoint com muitas falhas consecutivas de entrega é desativado automaticamente (reative manualmente na aba).
Gerenciar exige a permissão crm.webhooks.manage no painel.
Corpo da entrega
Seção intitulada “Corpo da entrega”{ "event": "lead.stage_changed", "occurred_at": "2026-09-08T18:15:52Z", "funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d", "lead": { "id": "01a0823b-cf5d-71fc-9177-bac53df79b12", "funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d", "stage_id": "c3d4e5f6-7a8b-4c9d-0e1f-2a3b4c5d6e7f", "contact_name": "Rogério Corona", "status": "negociando", "custom_fields": {} }, "context": { "from_stage_id": "a1c2d3e4-5f6a-4b7c-8d9e-0f1a2b3c4d5e", "to_stage_id": "c3d4e5f6-7a8b-4c9d-0e1f-2a3b4c5d6e7f" }}lead traz o shape de um Negócio (filtrado pelos “Campos enviados”, se configurado). context traz metadados específicos do evento — nunca filtrado por “Campos enviados”:
| Evento | context |
|---|---|
lead.stage_changed | { from_stage_id, to_stage_id } |
lead.won / lead.lost | { status } |
lead.updated | { changed: string[] } — nomes dos campos que mudaram |
lead.comment_added | { comment_id } |
lead.activity_created / lead.activity_completed | { activity_id, subject } |
| demais | {} |
Headers e verificação da assinatura
Seção intitulada “Headers e verificação da assinatura”| Header | Conteúdo |
|---|---|
X-Maestron-Event | O evento (lead.created, …). |
X-Maestron-Delivery | ID único desta tentativa de entrega. |
X-Maestron-Signature | sha256=<hex> — HMAC-SHA256 do corpo bruto, com o segredo do webhook. |
Verificação (Node.js):
const crypto = require('crypto');
function isValid(rawBody, signatureHeader, secret) { const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));}Use sempre o corpo bruto (antes de fazer JSON.parse) — reserializar o JSON pode mudar a ordem das chaves e quebrar a comparação.
Retentativas
Seção intitulada “Retentativas”Seu endpoint tem até 5 segundos para responder.
| Resposta | Comportamento |
|---|---|
2xx | Sucesso. |
4xx (exceto 429) | Erro seu — sem retentativa. |
429 ou 5xx ou timeout/rede | Retentativa exponencial: 1min, 5min, 30min, 2h, 6h, 24h (6 tentativas). |
Feed de eventos (polling)
Seção intitulada “Feed de eventos (polling)”A mesma fonte de dados dos webhooks, por API Key — sem precisar expor uma URL pública. Útil pra sincronizar seu sistema periodicamente ou como plano B se seu endpoint ficar fora do ar por muito tempo (a entrega por webhook não guarda histórico além das 6 tentativas).
Escopo: crm.view.
Listar eventos
Retorna eventos em ordem cronológica (mais antigo primeiro), paginado por cursor.
Parâmetros de query
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
funnel_id | string | — | Restringe a um funil. |
event | string | — | Restringe a um tipo de evento. |
cursor | string | — | Da resposta anterior (next_cursor) — continua de onde parou. |
limit | integer | — | Entre 1 e 100. Padrão 50. |
curl -X GET https://api.maestron.com.br/api/v1/crm/events \
-H "Authorization: Bearer SEU_TOKEN" <?php
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.maestron.com.br/api/v1/crm/events', [
'headers' => [
'Authorization' => 'Bearer SEU_TOKEN',
'Accept' => 'application/json',
],
]);
$data = json_decode($response->getBody(), true); import requests
headers = {"Authorization": "Bearer SEU_TOKEN"}
response = requests.get(
"https://api.maestron.com.br/api/v1/crm/events",
headers=headers,
)
data = response.json() const response = await fetch("https://api.maestron.com.br/api/v1/crm/events", {
method: "GET",
headers: {
"Authorization": "Bearer SEU_TOKEN",
},
});
const data = await response.json(); using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "SEU_TOKEN");
var response = await client.GetAsync("https://api.maestron.com.br/api/v1/crm/events");
var data = await response.Content.ReadAsStringAsync(); GET /crm/events HTTP/1.1
Host: api.maestron.com.br
Authorization: Bearer SEU_TOKEN Respostas
{
"message": "Eventos.",
"data": {
"items": [
{
"id": "01a08246-cd97-7300-bc9c-36c1b5d303cc",
"event": "lead.created",
"occurred_at": "2026-09-08T15:27:53Z",
"funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d",
"lead_id": "01a08246-c214-737b-b690-49910e76d62c",
"lead": {
"id": "01a08246-c214-737b-b690-49910e76d62c",
"contact_name": "Smoke Test Lead",
"status": "sem_contato"
},
"context": {}
}
],
"next_cursor": "eyJ2IjoiMjAyNi0wOS0wOFQxNTo..."
}
} {
"message": "Não autenticado.",
"data": null
} {
"message": "A chave não tem o escopo necessário.",
"data": null
} Sem next_cursor na resposta = não há mais eventos além dos retornados. Guarde o next_cursor e passe de volta na próxima chamada para continuar de onde parou — não filtre por occurred_at na mão.
Para o detalhamento de cada código de status, consulte Erros.