Webhooks
Com webhooks, o Pega Vez faz um POST no seu servidor quando algo acontece no salão — em vez de você consultar a
agenda de tempos em tempos.
Configurar
Seção intitulada “Configurar”Webhooks são gerenciados só no painel, em Configurações → Integrações → Webhooks, por um usuário logado com a
permissão Gerenciar integrações (integrations.manage). Essa permissão nunca vai num token: um token de API não
consegue criar, alterar nem ler webhooks.
Como os eventos levam dados de clientes para fora (nome, telefone, e-mail), criar, editar, ativar, enviar teste e ver
as entregas exigem também a permissão Ver clientes (customers.view) — o perfil Dono tem as duas. Só com
integrations.manage dá para listar, desativar, excluir e gerar um novo segredo.
- Informe a URL que vai receber os eventos:
https://, até 2000 caracteres, sem usuário e senha na URL, e acessível pela internet (veja Segurança). - Escolha os eventos e, se quiser, uma descrição (até 200 caracteres).
- Copie o segredo de assinatura (
whsec_...). Ele aparece uma única vez, na criação; o Pega Vez o guarda cifrado para assinar os envios.
Cada salão pode ter até 10 webhooks. No painel também dá para:
- Editar a URL, os eventos e a descrição. Editar não muda se o webhook está ativo ou desativado (só os botões de desativar e reativar mudam).
- Desativar e reativar. Desativar cancela as entregas pendentes; reativar zera a contagem de falhas, e o que ficou pendente de antes não é mais enviado (nada chega atrasado depois de reativar).
- Gerar um novo segredo (rotação). O novo segredo vale na hora e aparece uma única vez; o antigo para de valer no mesmo instante — não há período em que os dois convivam. Atualize o receptor logo em seguida (veja Trocar o segredo).
- Enviar teste: manda agora um evento
pinge mostra o resultado. Funciona mesmo com o webhook desativado. Um teste que falha não conta como falha; um teste com sucesso zera a contagem de falhas seguidas (o receptor voltou), sem reativar o webhook. - Ver as entregas dos últimos 30 dias, com cada tentativa: status, código HTTP, duração, erro e até 2 KB do corpo da sua resposta.
- Excluir o webhook, junto com o histórico de entregas.
Se o salão mudar para um plano sem integrações, o painel ainda deixa ver, desativar e excluir os webhooks (para limpar o que não vai mais usar); criar, editar, reativar, testar e gerar segredo voltam com um plano que tenha integrações.
Eventos
Seção intitulada “Eventos”| Evento | Quando acontece |
|---|---|
appointment.created |
Um agendamento foi criado: pela equipe, pela página pública, numa série recorrente, pela API ou pelo MCP. |
appointment.rescheduled |
Um agendamento mudou de horário ou de profissional. |
appointment.cancelled |
Um agendamento foi cancelado (pela equipe, pelo cliente, pelo link do lembrete, pela API, pelo MCP ou pela anonimização do cliente, que cancela os futuros). |
appointment.status_changed |
O status mudou: confirmado, chegou, em atendimento, concluído, não compareceu. O cancelamento sai só como appointment.cancelled. |
order.closed |
Uma comanda foi fechada. |
order.cancelled |
Uma comanda foi cancelada. |
customer.created |
Um cliente novo foi cadastrado (pela equipe, num agendamento com cliente novo ou pela página pública). |
ping |
Só no Enviar teste do painel; não dá para assinar. |
Os eventos nascem junto com a mudança, na mesma transação: valem para todo caminho (painel, página pública, links dos lembretes, séries, API e MCP) e nunca saem de uma mudança que não foi gravada.
Eventos só são gerados enquanto o webhook está ativo e assina aquele tipo. Também não são gerados se a conta do salão estiver bloqueada (assinatura inativa) ou num plano sem integrações; o que já tinha sido gerado segue as retentativas. Depois de reativar um webhook, use a API para buscar o que mudou no intervalo.
Envelope
Seção intitulada “Envelope”Todo evento tem o mesmo envelope, em JSON:
{ "id": "0b8e6f3a-5d21-4c7e-9f14-7a2c3d9e8b10", "type": "appointment.created", "createdAt": "2026-11-03T13:02:11.482913+00:00", "salonId": "3f6c2b1e-8d4a-4c4e-9b7a-2a5d0e9f1c11", "data": { }}| Campo | O que é |
|---|---|
id |
Identificador único do evento (UUID). Igual em todas as tentativas de entrega: use para descartar repetidos. |
type |
Um dos eventos da tabela acima. |
createdAt |
Quando o evento aconteceu. |
salonId |
Salão de origem. |
data |
O recurso afetado, no formato do tipo de evento (abaixo). |
Regras de formato, iguais para todos os eventos:
- campos em camelCase e enums como texto (
"Confirmed","Pix"); - instantes em UTC (ISO 8601 com
+00:00; frações de segundo podem vir ou não) — diferente da API, que devolve no fuso do salão. Converta paraAmerica/Sao_Paulopara exibir; - valores em reais (BRL), número com até 2 casas decimais;
- campos sem valor vêm como
null; - campos novos podem aparecer em
datasem aviso (veja compatibilidade): ignore o que não conhece.
Campos de data
Seção intitulada “Campos de data”Agendamento: appointment.*
Seção intitulada “Agendamento: appointment.*”Os quatro eventos de agendamento têm o mesmo data, com o estado do agendamento depois da mudança.
| Campo | Tipo | O que é |
|---|---|---|
id |
UUID | Agendamento. |
status |
enum | Requested, Confirmed, Arrived, InService, Completed, Cancelled ou NoShow. |
previousStatus |
enum ou null |
Só em appointment.status_changed: o status anterior. |
origin |
enum | Por onde entrou: Online (página pública), Reception (recepção) ou Professional (o próprio profissional). |
startsAt, endsAt |
instante | Início e fim. |
previousStartsAt |
instante ou null |
Só em appointment.rescheduled: o início anterior (igual ao atual se só o profissional mudou). |
previousProfessionalId |
UUID ou null |
Só em appointment.rescheduled: o profissional anterior (igual ao atual se só o horário mudou). |
professional |
objeto | id e name do profissional. |
customer |
objeto | id, name, phone (E.164, ex.: +5511999998888) e email. Cliente anonimizado sai como "Cliente anonimizado", sem telefone nem e-mail. |
services |
lista | serviceId, name, price (cobrado, já com promoção) e durationMinutes, na ordem do atendimento. |
totalPrice |
número | Soma dos preços cobrados. |
comboId, comboName |
UUID / texto ou null |
Combo, se o agendamento veio de um. |
seriesId |
UUID ou null |
Série recorrente, se faz parte de uma. |
arrivedAt, startedAt, completedAt, noShowAt, cancelledAt |
instante ou null |
Quando cada etapa aconteceu. |
cancelledBy |
enum ou null |
Customer ou Salon. |
cancellationReason |
texto ou null |
Motivo do cancelamento. |
createdAt, updatedAt |
instante | Criação e última alteração. |
{ "id": "0b8e6f3a-5d21-4c7e-9f14-7a2c3d9e8b10", "type": "appointment.rescheduled", "createdAt": "2026-11-03T13:02:11.482913+00:00", "salonId": "3f6c2b1e-8d4a-4c4e-9b7a-2a5d0e9f1c11", "data": { "id": "9a1d7c52-3e0f-4b8e-a6d2-5c4f1e7b9a30", "status": "Confirmed", "previousStatus": null, "origin": "Online", "startsAt": "2026-11-04T18:00:00+00:00", "endsAt": "2026-11-04T18:45:00+00:00", "previousStartsAt": "2026-11-04T17:00:00+00:00", "previousProfessionalId": "5b7e2c10-4a3d-4f8e-9c21-0d6a8e3f1b42", "professional": { "id": "e41f9a7c-2b6d-4c3e-8a15-7f0d2c9b6e83", "name": "Ana" }, "customer": { "id": "c2d8b4e6-7a1f-4e9c-b3d5-8f6a0e2c4b17", "name": "Maria Souza", "phone": "+5511999998888", "email": "maria@exemplo.com.br" }, "services": [ { "serviceId": "7f3a1c9e-5b2d-4e8f-a6c4-1d9e3b7f5a20", "name": "Corte feminino", "price": 80.00, "durationMinutes": 45 } ], "totalPrice": 80.00, "comboId": null, "comboName": null, "seriesId": null, "arrivedAt": null, "startedAt": null, "completedAt": null, "noShowAt": null, "cancelledAt": null, "cancelledBy": null, "cancellationReason": null, "createdAt": "2026-11-01T22:14:05.120394+00:00", "updatedAt": "2026-11-03T13:02:11.482913+00:00" }}Comanda: order.closed e order.cancelled
Seção intitulada “Comanda: order.closed e order.cancelled”| Campo | Tipo | O que é |
|---|---|---|
id |
UUID | Comanda. |
number |
inteiro | Número da comanda no salão. |
status |
enum | Closed ou Cancelled (Open não gera evento). |
customer |
objeto ou null |
id e name do cliente (sem contatos). |
businessDate |
data | Data de movimento, no fuso do salão (AAAA-MM-DD). |
openedAt, closedAt, cancelledAt |
instante ou null |
Abertura, fechamento e cancelamento. |
cancellationReason |
texto ou null |
Motivo do cancelamento. |
subtotal |
número | Soma dos itens antes dos descontos. |
discountTotal |
número | Descontos. |
total |
número | Total da comanda (sem gorjeta). |
tipTotal |
número | Gorjetas. |
paidTotal |
número | Pago. |
balance |
número | Falta receber (fiado): total + tipTotal − paidTotal. |
items |
lista | kind (Service, Product ou Custom), description, quantity, unitPrice, netAmount (depois dos descontos), professionalId, serviceId, productId e appointmentId (os ids podem ser null). |
payments |
lista | method (nome da forma de pagamento no salão), kind (Cash, Pix, Debit, Credit ou Other), amount, installments, status (Active ou Reversed — estornado no cancelamento) e paidAt. |
tips |
lista | professionalId e amount de cada gorjeta. |
{ "id": "4c9e2a7d-1b3f-4d8e-a5c6-9e0f7b2d3a14", "type": "order.closed", "createdAt": "2026-11-04T19:05:42.017356+00:00", "salonId": "3f6c2b1e-8d4a-4c4e-9b7a-2a5d0e9f1c11", "data": { "id": "a8f3c1e5-6d2b-4a9f-8e7c-3b5d1f9a0c62", "number": 1287, "status": "Closed", "customer": { "id": "c2d8b4e6-7a1f-4e9c-b3d5-8f6a0e2c4b17", "name": "Maria Souza" }, "businessDate": "2026-11-04", "openedAt": "2026-11-04T18:02:10.441208+00:00", "closedAt": "2026-11-04T19:05:41.998127+00:00", "cancelledAt": null, "cancellationReason": null, "subtotal": 125.00, "discountTotal": 5.00, "total": 120.00, "tipTotal": 10.00, "paidTotal": 130.00, "balance": 0.00, "items": [ { "kind": "Service", "description": "Corte feminino", "quantity": 1, "unitPrice": 80.00, "netAmount": 80.00, "professionalId": "e41f9a7c-2b6d-4c3e-8a15-7f0d2c9b6e83", "serviceId": "7f3a1c9e-5b2d-4e8f-a6c4-1d9e3b7f5a20", "productId": null, "appointmentId": "9a1d7c52-3e0f-4b8e-a6d2-5c4f1e7b9a30" }, { "kind": "Product", "description": "Máscara hidratante", "quantity": 1, "unitPrice": 45.00, "netAmount": 40.00, "professionalId": null, "serviceId": null, "productId": "d6b0e3a9-8c4f-4b1d-9e2a-5f7c3d1b8e46", "appointmentId": null } ], "payments": [ { "method": "Pix", "kind": "Pix", "amount": 130.00, "installments": 1, "status": "Active", "paidAt": "2026-11-04T19:05:30.512873+00:00" } ], "tips": [ { "professionalId": "e41f9a7c-2b6d-4c3e-8a15-7f0d2c9b6e83", "amount": 10.00 } ] }}Cliente: customer.created
Seção intitulada “Cliente: customer.created”| Campo | Tipo | O que é |
|---|---|---|
id |
UUID | Cliente. |
name |
texto | Nome. |
phone |
texto ou null |
Telefone normalizado em E.164 (ex.: +5511999998888). |
email |
texto ou null |
E-mail em minúsculas. |
hasAccount |
booleano | O cadastro está ligado a uma conta de cliente (agendou logado na página pública). |
createdAt |
instante | Quando foi cadastrado. |
Documento, data de nascimento e observações internas nunca vão no webhook.
Quando o salão anonimiza um cliente (LGPD), os eventos dele guardados no Pega Vez (customer.created, appointment.*
e order.* dos últimos 30 dias) passam a ter o nome "Cliente anonimizado", sem telefone, e-mail nem o motivo que o
cliente escreveu ao cancelar. Entregas ainda pendentes saem já assim; o que você recebeu antes continua com você
(apague do seu lado também, se for o caso).
{ "id": "e7b1d3f5-9a2c-4e6b-8d0f-2c4a6e8b1d39", "type": "customer.created", "createdAt": "2026-11-01T22:14:05.120394+00:00", "salonId": "3f6c2b1e-8d4a-4c4e-9b7a-2a5d0e9f1c11", "data": { "id": "c2d8b4e6-7a1f-4e9c-b3d5-8f6a0e2c4b17", "name": "Maria Souza", "phone": "+5511999998888", "email": "maria@exemplo.com.br", "hasAccount": false, "createdAt": "2026-11-01T22:14:05.120394+00:00" }}Teste: ping
Seção intitulada “Teste: ping”Enviado pelo botão Enviar teste do painel, com uma tentativa só. Use para conferir a URL e a verificação da assinatura.
| Campo | Tipo | O que é |
|---|---|---|
subscriptionId |
UUID | O webhook testado. |
message |
texto | "Teste de webhook do Pega Vez." |
{ "id": "1f5a9c3e-7b2d-4f8a-9c6e-0d4b2f8a6c51", "type": "ping", "createdAt": "2026-11-03T13:10:00.204517+00:00", "salonId": "3f6c2b1e-8d4a-4c4e-9b7a-2a5d0e9f1c11", "data": { "subscriptionId": "b3e7a1c9-5d2f-4b8e-a0c6-9f1d3e5b7a28", "message": "Teste de webhook do Pega Vez." }}Headers
Seção intitulada “Headers”POST /webhooks/pegavez HTTP/1.1Host: seu-servidor.exemplo.comContent-Type: application/json; charset=utf-8User-Agent: PegaVez-Webhooks/1.0Pegavez-Signature: t=1793710932,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdPegavez-Event-Id: 0b8e6f3a-5d21-4c7e-9f14-7a2c3d9e8b10Pegavez-Event-Type: appointment.rescheduled| Header | O que é |
|---|---|
Content-Type |
application/json (corpo em UTF-8). |
User-Agent |
PegaVez-Webhooks/1.0. |
Pegavez-Signature |
Assinatura do corpo: t=<segundos Unix>,v1=<hex>. Veja Assinatura. |
Pegavez-Event-Id |
O mesmo id do envelope; igual em todas as tentativas. Dá para descartar repetidos antes de ler o corpo. |
Pegavez-Event-Type |
O mesmo type do envelope. |
Assinatura
Seção intitulada “Assinatura”Pegavez-Signature: t=1793710932,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdt: momento do envio desta tentativa, em segundos Unix.v1: HMAC-SHA256, em hexadecimal minúsculo, de"{t}.{corpo}"— o valor det, um ponto e o corpo cru da requisição, exatamente os bytes recebidos —, usando como chave o segredo do webhook (whsec_...inteiro, com o prefixo) em UTF-8.
Para verificar:
- Leia o corpo cru, antes de qualquer parse de JSON (reserializar muda os bytes e a assinatura não bate).
- Separe
tev1do header. Hoje vem um únicov1, mas trate o header como lista e aceite se qualquerv1conferir: assim a sua verificação continua valendo se o formato ganhar assinaturas extras. - Recuse se
testiver a mais de 5 minutos do seu relógio (proteção contra repetição). Mantenha o relógio do servidor sincronizado (NTP). - Calcule o HMAC-SHA256 de
"{t}.{corpo}"com o segredo e compare com ov1em tempo constante (nunca com==de texto).
Os exemplos abaixo seguem esses passos.
using System.Security.Cryptography;using System.Text;using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);var app = builder.Build();var secret = app.Configuration["Pegavez:WebhookSecret"]!;
app.MapPost("/webhooks/pegavez", async (HttpRequest request) =>{ using var buffer = new MemoryStream(); await request.Body.CopyToAsync(buffer); var body = buffer.ToArray();
if (!PegavezSignature.IsValid(request.Headers["Pegavez-Signature"].ToString(), body, secret)) return Results.Unauthorized();
var evt = JsonSerializer.Deserialize<JsonElement>(body); // Guarde evt (descartando ids repetidos) e processe fora da requisição. return Results.Ok();});
app.Run();
static class PegavezSignature{ static readonly TimeSpan Tolerance = TimeSpan.FromMinutes(5);
public static bool IsValid(string header, byte[] body, string secret) { long? timestamp = null; var signatures = new List<string>(); foreach (var part in header.Split(',')) { var pair = part.Split('=', 2, StringSplitOptions.TrimEntries); if (pair.Length != 2) continue; if (pair[0] == "t" && long.TryParse(pair[1], out var t)) timestamp = t; else if (pair[0] == "v1") signatures.Add(pair[1]); } if (timestamp is null || signatures.Count == 0) return false;
var age = DateTimeOffset.UtcNow - DateTimeOffset.FromUnixTimeSeconds(timestamp.Value); if (age.Duration() > Tolerance) return false;
byte[] signed = [.. Encoding.UTF8.GetBytes($"{timestamp}."), .. body]; var expected = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), signed);
foreach (var signature in signatures) { byte[] received; try { received = Convert.FromHexString(signature); } catch (FormatException) { continue; } if (CryptographicOperations.FixedTimeEquals(expected, received)) return true; } return false; }}import crypto from 'node:crypto';import express from 'express';
const app = express();const secret = process.env.PEGAVEZ_WEBHOOK_SECRET;
// express.raw: o corpo chega como Buffer, sem parse (necessário para a assinatura).app.post('/webhooks/pegavez', express.raw({ type: 'application/json' }), (req, res) => { if (!verificarAssinatura(req.get('Pegavez-Signature'), req.body, secret)) { return res.sendStatus(401); } const evento = JSON.parse(req.body.toString('utf8')); // Guarde o evento (descartando ids repetidos) e processe fora da requisição. res.sendStatus(200);});
function verificarAssinatura(header, corpo, segredo, toleranciaSegundos = 300) { if (!header) return false; let t; const assinaturas = []; for (const parte of header.split(',')) { const [chave, valor] = parte.trim().split('=', 2); if (chave === 't') t = Number(valor); else if (chave === 'v1' && valor) assinaturas.push(valor); } if (!Number.isInteger(t) || assinaturas.length === 0) return false; if (Math.abs(Date.now() / 1000 - t) > toleranciaSegundos) return false;
const esperado = crypto.createHmac('sha256', segredo).update(`${t}.`).update(corpo).digest(); return assinaturas.some((assinatura) => { const recebido = Buffer.from(assinatura, 'hex'); return recebido.length === esperado.length && crypto.timingSafeEqual(recebido, esperado); });}
app.listen(3000);import hashlibimport hmacimport jsonimport osimport time
from flask import Flask, abort, request
app = Flask(__name__)SEGREDO = os.environ["PEGAVEZ_WEBHOOK_SECRET"]
def verificar_assinatura(header: str | None, corpo: bytes, segredo: str, tolerancia: int = 300) -> bool: if not header: return False t, assinaturas = None, [] for parte in header.split(","): chave, _, valor = parte.strip().partition("=") if chave == "t" and valor.isdigit(): t = int(valor) elif chave == "v1" and valor: assinaturas.append(valor.lower()) if t is None or not assinaturas: return False if abs(time.time() - t) > tolerancia: return False
esperado = hmac.new(segredo.encode(), f"{t}.".encode() + corpo, hashlib.sha256).hexdigest() return any(hmac.compare_digest(esperado, assinatura) for assinatura in assinaturas)
@app.post("/webhooks/pegavez")def receber(): corpo = request.get_data() # bytes crus, antes de qualquer parse if not verificar_assinatura(request.headers.get("Pegavez-Signature"), corpo, SEGREDO): abort(401) evento = json.loads(corpo) # Guarde o evento (descartando ids repetidos) e processe fora da requisição. return "", 200Para testar a sua verificação, calcule uma assinatura na linha de comando:
corpo='{"id":"0b8e6f3a-5d21-4c7e-9f14-7a2c3d9e8b10","type":"customer.created"}'t=$(date +%s)v1=$(printf '%s.%s' "$t" "$corpo" | openssl dgst -sha256 -hmac "$PEGAVEZ_WEBHOOK_SECRET" -hex | sed 's/^.* //')
curl -s https://seu-servidor.exemplo.com/webhooks/pegavez \ -H "Content-Type: application/json" \ -H "Pegavez-Signature: t=$t,v1=$v1" \ -d "$corpo"Responder
Seção intitulada “Responder”- Responda 2xx em até 10 segundos, contando o envio do corpo inteiro da resposta: mandar os headers e demorar no corpo também estoura o prazo. Qualquer outro status, tempo esgotado ou erro de conexão conta como falha.
- Redirecionamentos (3xx) não são seguidos e contam como falha: cadastre a URL final.
- Não processe nada pesado dentro da requisição: grave o evento numa fila ou tabela, responda
200e processe depois. - O corpo da sua resposta é ignorado, mas os primeiros 2 KB aparecem no histórico de entregas do painel: uma mensagem de erro curta ajuda quem for investigar.
Retentativas e desativação
Seção intitulada “Retentativas e desativação”Cada entrega que falha é tentada de novo, até 8 tentativas ao todo, nestes instantes contados a partir da criação do evento:
| Tentativa | 1ª | 2ª | 3ª | 4ª | 5ª | 6ª | 7ª | 8ª |
|---|---|---|---|---|---|---|---|---|
| Depois do evento | imediata | 1 min | 5 min | 30 min | 2 h | 5 h | 10 h | 24 h |
- Todas as tentativas do mesmo evento têm o mesmo
id(e o mesmoPegavez-Event-Id); a cada tentativa,te a assinatura são recalculados. - Depois da 8ª falha, a entrega fica como falhou no histórico e não é mais tentada.
- O webhook é desativado automaticamente quando está falhando sem parar (nenhuma entrega com sucesso no meio) há pelo menos 24 horas com 20 ou mais tentativas seguidas falhando, ou há 3 dias. Uma queda de alguns minutos com muitos eventos na fila não desativa. O motivo aparece em Configurações → Integrações → Webhooks, e as entregas pendentes dele são canceladas. Corrija o receptor e reative no painel (a contagem de falhas recomeça do zero). Os eventos do período em que ficou desativado não são reenviados: busque o estado atual na API.
- “Sem parar” conta só o tempo com tentativas: se passaram mais de 25 horas desde a falha anterior (dias sem nenhum evento, por exemplo), o período “falhando” recomeça na falha atual. Um Enviar teste com sucesso também zera a contagem.
- Eventos e entregas ficam guardados por 30 dias.
Ordem e duplicidade
Seção intitulada “Ordem e duplicidade”A entrega é pelo menos uma vez e sem ordem garantida:
- o mesmo evento pode chegar mais de uma vez — descarte pelo
id(ou pelo headerPegavez-Event-Id); - um
appointment.rescheduledpode chegar antes doappointment.createdcorrespondente se a primeira entrega falhou, e eventos de um mesmo agendamento podem chegar trocados — compare oupdatedAtdodatacom o que você já tem e, na dúvida, busque o estado atual do recurso na API.
Trocar o segredo
Seção intitulada “Trocar o segredo”A rotação no painel troca o segredo na hora: a partir daquele instante, os envios (inclusive as retentativas de eventos antigos) são assinados só com o segredo novo, e o antigo deixa de valer. Para não perder eventos:
- Gere o novo segredo no painel e copie.
- Atualize o receptor o quanto antes. Se precisar de folga, deixe o receptor aceitar os dois segredos (o novo e o antigo) por alguns minutos e depois remova o antigo.
- Use Enviar teste para confirmar que a verificação passa.
Eventos que falharem nesse intervalo são reenviados pelas retentativas, já com o segredo novo.
Segurança
Seção intitulada “Segurança”- Sempre verifique a assinatura e a janela de 5 minutos antes de confiar no conteúdo.
- Guarde o segredo como qualquer outra credencial (cofre ou variável de ambiente), nunca no código.
- Suspeita de vazamento: gere um segredo novo no painel e atualize o receptor.
- A URL precisa ser pública. Na hora de cadastrar,
localhost,*.localhoste IPs internos são recusados; na hora de enviar, o Pega Vez resolve o DNS e não conecta em endereços internos (loopback, redes privadas, link-local, CGNAT, documentação, multicast e reservados). Em IPv6, só endereços globais (2000::/3) passam, sem 6to4, Teredo e NAT64. Uma URL que passe a apontar para a rede interna falha com “Bloqueado: o endereço aponta para uma rede interna.”